Поля с ошибками в классе Python @dataclass

Как заставить это поднять исключение при установке полей с ошибками в @dataclassкласс Python?

Я хочу практичный способ сделать это. Нужно ли мне писать свой собственный декоратор?

@dataclass
class C(object):
    x: int = 1

obj = C()
obj.y = 2  # should raise an exception

3 ответа

Одним простым способом (который работает с любым классом) является определение __slots__:

In [1]: from dataclasses import dataclass

In [2]: @dataclass
   ...: class Foo:
   ...:     __slots__ = 'bar','baz'
   ...:     bar: int
   ...:     baz: int
   ...:

In [3]: foo = Foo(42, 88)

In [4]: foo.biz = 10
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-d52b60444257> in <module>()
----> 1 foo.biz = 10

AttributeError: 'Foo' object has no attribute 'biz'

Целью слотов является небольшая оптимизация. Это позволяет экземплярам класса использовать таблицу символов вместо dict в качестве пространства имен класса. Это немного увеличивает скорость доступа к атрибутам и может значительно улучшить использование памяти для каждого экземпляра (поскольку экземпляр не переносит dict под капотом), однако, он запрещает динамическую настройку атрибутов.

Это на самом деле моя любимая особенность __slots__,

Обратите внимание, что вы должны соблюдать осторожность при использовании наследования со слотами, по крайней мере, если вы хотите, чтобы подклассы сохраняли поведение слотов.

Один из способов сделать это - пометить класс данных как frozen:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True)
... class C:
...     x: int = 1
...     
>>> c = C()
>>> c.y = 1
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'y'

Но обратите внимание, что это делает все существующие атрибуты, например x в этом случае тоже только для чтения.

Вы можете предоставить свой собственный __setattr__() метод, который позволяет присваивать только известные поля.

Пример:

      @dataclass
class C:
    x: int = 1

    def __setattr__(self, k, v): 
        if k not in self.__annotations__: 
            raise AttributeError(f'{self.__class__.__name__} dataclass has no field {k}')
        super().__setattr__(k, v)

Тогда не работает / работает так:

      [ins] In [3]: obj = C() 
         ...: obj.y = 2                                                                                                                                                                       
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-7a568eb098b1> in <module>
      1 obj = C()
----> 2 obj.y = 2

<ipython-input-2-d30972f86fbb> in __setattr__(self, k, v)
      5     def __setattr__(self, k, v):
      6         if k not in self.__annotations__:
----> 7             raise AttributeError(f'{self.__class__.__name__} dataclass has no field {k}')
      8         super().__setattr__(k, v)
      9 

AttributeError: C dataclass has no field y

[ins] In [4]: obj.x = 23                                                                                                                                                                      

[ins] In [5]: obj.x                                                                                                                                                                           
Out[5]: 23
Другие вопросы по тегам