Классы данных - это механизм, обеспечивающий инициализацию по умолчанию для принятия атрибутов в качестве параметров, и хорошее представление, а также некоторые тонкости, такие как __post_init__
hook.
К счастью, они не связываются с любым другим механизмомдля доступа к атрибутам в Python - и вы по-прежнему можете создавать свои атрибуты dataclassess как 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
и автоматическиобратите внимание на типы - чтобы параметр типа не был необходим для самого класса MyAttr
.Но, как я уже говорил, вы можете сделать это настолько изощренно, насколько захотите.