Классы данных — это механизм, обеспечивающий инициализацию по умолчанию для принятия атрибутов в качестве параметров и красивое представление, а также некоторые тонкости, такие как хук __post_init__
.
К счастью, они не связываются ни с каким другим механизмом доступа к атрибутам в Python, и вы по-прежнему можете создавать свои атрибуты dataclasses как дескрипторы property
или собственный класс дескрипторов, если хотите. Таким образом, любой доступ к атрибуту будет проходить через ваши функции получения и установки автоматически.
Единственный недостаток использования встроенного property
по умолчанию заключается в том, что вы должны использовать его «по-старому», а не с синтаксисом декоратора, который позволяет создавать аннотации для ваших атрибутов.
Таким образом, «дескрипторы» — это специальные объекты, назначенные атрибутам класса в Python таким образом, что любой доступ к этому атрибуту вызовет методы дескрипторов __get__
, __set__
или __del__
. Встроенный property
удобен для создания дескриптора, передающего от 1 до 3 функций, которые будут вызываться из этих методов.
Итак, без пользовательского дескриптора вы можете сделать:
@dataclass
class MyClass:
def setname(self, value):
if not isinstance(value, str):
raise TypeError(...)
self.__dict__["name"] = value
def getname(self):
return self.__dict__.get("name")
name: str = property(getname, setname)
# optionally, you can delete the getter and setter from the class body:
del setname, getname
Используя этот подход, вам придется писать доступ к каждому атрибуту как два метода/функции, но вам больше не нужно будет писать свой __post_init__
: каждый атрибут будет проверять себя.
Также обратите внимание, что в этом примере был использован обычный подход к сохранению атрибутов в файле экземпляра __dict__
. В примерах в Интернете практика заключается в использовании обычного доступа к атрибутам, но с добавлением перед именем _
. Это приведет к тому, что эти атрибуты загрязнят dir
в вашем последнем экземпляре, а частные атрибуты останутся незащищенными.
Другой подход состоит в том, чтобы написать свой собственный класс дескриптора и позволить ему проверять экземпляр и другие свойства атрибутов, которые вы хотите защитить. Это может быть настолько сложным, насколько вы хотите, кульминацией которого станет ваша собственная структура. Таким образом, для класса дескриптора, который будет проверять тип атрибута и принимать список валидаторов, вам потребуется:
def positive_validator(name, value):
if value <= 0:
raise ValueError(f"values for {name!r} have to be positive")
class MyAttr:
def __init__(self, type, validators=()):
self.type = type
self.validators = validators
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if not instance: return self
return instance.__dict__[self.name]
def __delete__(self, instance):
del instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError(f"{self.name!r} values must be of type {self.type!r}")
for validator in self.validators:
validator(self.name, value)
instance.__dict__[self.name] = value
#And now
@dataclass
class Person:
name: str = MyAttr(str)
age: float = MyAttr((int, float), [positive_validator,])
Вот и все — создание собственного класса дескриптора требует немного больше знаний о Python, но приведенный выше код должен быть удобен для использования даже в производственной среде — вы можете его использовать.
Обратите внимание, что вы можете легко добавить множество других проверок и преобразований для каждого из ваших атрибутов, а код в самом __set_name__
можно изменить так, чтобы он анализировал __annotations__
в классе owner
для автоматического принятия к сведению типов, чтобы параметр type не нужны для самого класса MyAttr
. Но, как я уже говорил, вы можете сделать это настолько изощренным, насколько захотите.
person
jsbueno
schedule
02.02.2019
@dataclass(frozen=True)
, чтобы сделать его неизменным - person juanpa.arrivillaga   schedule 02.02.2019property
или чем-то еще, однако это убрало бы часть приятности класса данных для начала. - person juanpa.arrivillaga   schedule 02.02.2019