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

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

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

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

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

person porton    schedule 14.12.2018    source источник
comment
Не могли бы вы привести пример?   -  person Patrick Haugh    schedule 14.12.2018
comment
Пока вам не нужно изменять свои классы после создания, настройка @dataclass(frozen=True) также работает и по-прежнему позволяет вам устанавливать значения по умолчанию.   -  person Arne    schedule 11.03.2019
comment
@Arne Вы должны опубликовать это как ответ.   -  person wim    schedule 21.11.2019


Ответы (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__.

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

person juanpa.arrivillaga    schedule 14.12.2018
comment
Я отредактировал ответ, чтобы показать, что он не работает - person porton; 14.12.2018
comment
@porton да, потому что вы не можете использовать переменную класса (статическую) со слотами. Если вам нужно значение по умолчанию, используйте фабрику по умолчанию. - person juanpa.arrivillaga; 14.12.2018
comment
Это довольно ограниченное решение, так как оно не работает с классами данных со значениями по умолчанию. - person maxschlepzig; 06.03.2021

Один из способов сделать это — пометить класс данных как 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 в данном случае, также доступными только для чтения.

person Arne    schedule 21.11.2019

Вы можете предоставить свой собственный метод __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
person maxschlepzig    schedule 06.03.2021