Я читал на Python 3.7 dataclass в качестве альтернативы namedtuples (что я обычно использую при группировании данных в структуре). Мне было интересно, совместим ли dataclass с декоратором свойств, чтобы определить функции getter и setter для элементов данных в dataclass. Если да, то это описано где-то? Или есть примеры?
Python Dataclass и декоратор свойств
Ответ 1
Он уверен, что работает:
from dataclasses import dataclass
@dataclass
class Test:
_name: str="schbell"
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, v: str) -> None:
self._name = v
t = Test()
print(t.name) # schbell
t.name = "flirp"
print(t.name) # flirp
print(t) # Test(_name='flirp')
На самом деле, почему это не так? В конце концов, то, что вы получаете, это просто старый добрый класс, полученный от типа:
print(type(t)) # <class '__main__.Test'>
print(type(Test)) # <class 'type'>
Возможно, именно поэтому свойства нигде не упоминаются конкретно. Однако в PEP-557 Abstract упоминается общая пригодность известных функций класса Python:
Поскольку классы данных используют стандартный синтаксис определения классов, вы можете использовать наследование, метаклассы, docstrings, определяемые пользователем методы, фабрики классов и другие функции класса Python.
Ответ 2
В настоящее время лучший способ, который я нашел, - создать отдельный класс данных, содержащий свойство.
from dataclasses import dataclass, field
@dataclass
class _A:
x: int = 0
y: int = 1
class A(_A):
@property
def x(self) -> int:
return self._x
@x.setter
def x(self, value: int):
self._x = value
Класс ведет себя как обычный класс данных. И правильно определит поля __repr__
и __init__
(Vehicle(wheels=4)
вместо Vehicle(_wheels=4)
. Недостатком является то, что свойства не могут быть доступны только для чтения.
Этот пост пытается перезаписать атрибут dataclass wheel с помощью property
с тем же именем.
Однако @property
перезаписывает значение по умолчанию field
, что приводит к неожиданному поведению.
from dataclasses import dataclass, field
@dataclass
class A:
x: int
# same as: 'x = property(x) # Overwrite any field() info'
@property
def x(self) -> int:
return self._x
@x.setter
def x(self, value: int):
self._x = value
A() # 'A(x=<property object at 0x7f0cf64e5fb0>)' Oups
print(A.__dataclass_fields__) # {'x': Field(name='x',type=<class 'int'>,default=<property object at 0x>,init=True,repr=True}
Одним из способов решения этой проблемы, избегая наследования, было бы перезапись поля вне определения класса после вызова метакласса класса данных.
@dataclass
class A:
x: int
def x_getter(self):
return self._x
def x_setter(self, value):
self._x = value
A.x = property(x_getter)
A.x = A.x.setter(x_setter)
print(A(x=1))
print(A()) # missing 1 required positional argument: 'x'
Вероятно, можно будет перезаписать это автоматически, создав некоторый пользовательский метакласс и установив несколько field(metadata={'setter': _x_setter, 'getter': _x_getter})
.