dataclasses — Классы данных
Исходный код: Lib/dataclasses.py
Этот модуль предоставляет декоратор и функции для автоматического добавления сгенерированных специальных методов, таких как __init__() и __repr__(), в пользовательские классы. Он изначально был описан в PEP 557.
Переменные членов, используемые в этих сгенерированных методах, определяются с помощью аннотаций типов PEP 526. Например, этот код:
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
будет добавлять, помимо прочего, метод __init__(), который выглядит так:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
Обратите внимание, что этот метод добавляется автоматически в класс: он не указан напрямую в определении InventoryItem , показанном выше.
Введено в версии 3.7.
Модуль содержимого
-
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) -
Эта функция — декоратор, используемый для добавления сгенерированных специальных методов к классам, как описано ниже.
Декоратор
dataclass()анализирует класс для поискаfield.fieldопределяется как переменная класса, имеющая аннотацию типа. За исключением двух случаев, описанных ниже,dataclass()не анализирует тип, указанный в аннотации переменной.Порядок полей во всех сгенерированных методах — это порядок, в котором они появляются в определении класса.
Декоратор
dataclass()добавит различные методы «дундер» в класс, описанные ниже. Если любой из добавленных методов уже существует в классе, поведение зависит от параметра, как документировано ниже. Декоратор возвращает тот же класс, к которому он применяется; новый класс не создаётся.Если
dataclass()используется только как простой декоратор без параметров, он действует так, как если бы он имел значения по умолчанию, документированные в этом сигнатуре. То есть, эти три использованияdataclass()эквивалентны:@dataclass class C: ... @dataclass() class C: ... @dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) class C: ...Параметры
dataclass():-
init: Если значение true (по умолчанию), будет сгенерирован метод__init__().Если класс уже определяет
__init__(), этот параметр игнорируется. -
repr: Если значение true (по умолчанию), будет сгенерирован метод__repr__(). Сгенерированная строка repr будет содержать имя класса и имя и repr каждого поля в порядке их определения в классе. Поля, помеченные как исключаемые из repr, не включаются. Например:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).Если класс уже определяет
__repr__(), этот параметр игнорируется. -
eq: Если значение true (по умолчанию), будет сгенерирован метод__eq__(). Этот метод сравнивает класс, как если бы он был кортежем из его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа.Если класс уже определяет
__eq__(), этот параметр игнорируется. -
order: Если значение true (по умолчаниюFalse), будут сгенерированы методы__lt__(),__le__(),__gt__()и__ge__(). Эти методы сравнивают класс, как если бы он был кортежем из его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа. Еслиorderравно true, аeqравно false, генерируетсяValueError.Если класс уже определяет любой из методов
__lt__(),__le__(),__gt__()или__ge__(), то генерируетсяTypeError. -
unsafe_hash: ЕслиFalse(по умолчанию), сгенерируется метод__hash__()в соответствии с настройкамиeqиfrozen.Метод
__hash__()используется встроенной функциейhash()и при добавлении объектов в хэшированные коллекции, такие как словари и множества. Наличие метода__hash__()подразумевает неизменяемость экземпляров класса. Изменяемость — это сложная характеристика, зависящая от намерения программиста, существования и поведения метода__eq__()и значений флаговeqиfrozenв декоратореdataclass().По умолчанию,
dataclass()не будет неявно добавлять метод__hash__(), если это безопасно. Он также не будет добавлять или изменять явно определённый метод__hash__(). Установка атрибута класса__hash__ = Noneимеет определённый смысл для Python, как описано в документации к методу__hash__().Если метод
__hash__()не определён явно или равенNone, тогдаdataclass()может добавить неявный метод__hash__(). Хотя это не рекомендуется, вы можете заставитьdataclass()создать метод__hash__()сunsafe_hash=True. Это может быть случай, если ваш класс логически неизменяемый, но всё же может быть изменён. Это специализированный случай, который следует тщательно продумать.Вот правила, определяющие неявное создание метода
__hash__(). Обратите внимание, что вы не можете иметь как явный метод__hash__()в вашем dataclass, так и установитьunsafe_hash=True; это приведёт кTypeError.Если
eqиfrozenоба true, по умолчаниюdataclass()сгенерирует метод__hash__()для вас. Еслиeqравно true, аfrozenравно false,__hash__()будет установлено вNone, помечая его как нехэшируемый (что он и есть, поскольку он изменяемый). Еслиeqравно false,__hash__()останется без изменений, что означает, что будет использован метод__hash__()родительского класса (если родительский класс —object, это означает, что он будет использовать хеширование на основе id). -
frozen: Если значение true (по умолчаниюFalse), присвоение поля сгенерирует исключение. Это эмулирует чтение только для чтения неизменяемые экземпляры. Если__setattr__()или__delattr__()определены в классе, то генерируетсяTypeError. См. обсуждение ниже. -
match_args: Если значение true (по умолчаниюTrue), кортеж__match_args__будет создан из списка параметров сгенерированного метода__init__()(даже если__init__()не сгенерирован, см. выше). Если значение false или если__match_args__уже определён в классе, то__match_args__не будет сгенерирован.
Новое в версии 3.10.
-
-
kw_only: Если истинно (значение по умолчанию —False), все поля будут помечены как только для ключевых слов. Если поле помечено как только для ключевых слов, то единственное последствие состоит в том, что параметр__init__(), сгенерированный из поля, только для ключевых слов, должен быть указан с ключевым словом при вызове__init__(). Это никак не повлияет на другие аспекты dataclasses. См. запись в глоссарии параметра для получения подробностей. Также см. разделKW_ONLY.
Новая версия 3.10.
-
slots: Если истинно (значение по умолчанию —False), атрибут__slots__будет сгенерирован, и вместо исходного класса будет возвращен новый. Если__slots__уже определен в классе, то будет поднята ошибкаTypeError.
Новая версия 3.10.
Изменено в версии 3.11: Если имя поля уже включено в
__slots__базового класса, оно не будет включено в сгенерированное__slots__для предотвращения их перезаписи. Поэтому не используйте__slots__для получения имен полей dataclass. Используйтеfields()вместо этого. Для определения унаследованных слотов базовый класс__slots__может быть любым итерируемым объектом, но не итератором.-
weakref_slot: Если истинно (значение по умолчанию —False), добавляется слот с именем «__weakref__», необходимый для возможности создания слабой ссылки на экземпляр. Указаниеweakref_slot=Trueбез одновременного указанияslots=Trueявляется ошибкой.
Новая версия 3.11.
fieldмогут дополнительно указать значение по умолчанию, используя обычный синтаксис Python:@dataclass class C: a: int # 'a' has no default value b: int = 0 # assign a default value for 'b'В этом примере, и
a, иbбудут включены в сгенерированный метод__init__(), который будет определён как:def __init__(self, a: int, b: int = 0):
Если за полем с значением по умолчанию следует поле без значения по умолчанию, будет поднята ошибка
TypeError. Это верно как для одного класса, так и в результате наследования классов.-
-
dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING) -
Для обычных и простых случаев использования другой функциональности не требуется. Однако некоторые особенности dataclass требуют дополнительной информации для каждого поля. Чтобы удовлетворить эту потребность в дополнительной информации, вы можете заменить значение поля по умолчанию вызовом предоставленной функции
field(). Например:@dataclass class C: mylist: list[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]Как показано выше, значение
MISSING— это специальный объект, используемый для определения того, были ли некоторые параметры предоставлены пользователем. Этот специальный объект используется потому, чтоNone— это допустимое значение для некоторых параметров с другим значением. Ни один код не должен напрямую использовать значениеMISSING.Параметры
field():-
default: Если указано, это будет значение по умолчанию для данного поля. Это необходимо, потому что сам вызовfield()заменяет обычное расположение значения по умолчанию. -
default_factory: Если указано, это должна быть функция без аргументов, которая будет вызываться при необходимости значения по умолчанию для этого поля. Среди прочего, это может использоваться для задания полей с изменяемыми значениями по умолчанию, как обсуждалось ниже. Нельзя одновременно указатьdefaultиdefault_factory. -
init: Если истинно (по умолчанию), это поле включается в качестве параметра в сгенерированный метод__init__(). -
repr: Если истинно (по умолчанию), это поле включается в строку, возвращаемую сгенерированным методом__repr__(). -
hash: Может быть булевым значением илиNone. Если истинно, это поле включается в сгенерированный метод__hash__(). ЕслиNone(по умолчанию), используется значениеcompare; обычно это ожидаемое поведение. Поле должно учитываться в хеше, если оно используется для сравнения. Не рекомендуется устанавливать это значение на что-то отличное отNone.Одна возможная причина установить
hash=False, ноcompare=True, может быть в том, что поле является дорогостоящим для вычисления значения хеша, это поле необходимо для проверки на равенство, и есть другие поля, которые вносят вклад в хеш значения типа. Даже если поле исключается из хеша, оно по-прежнему будет использоваться для сравнения. -
compare: Если истинно (по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (__eq__(),__gt__()и др.). -
metadata: Может быть словарем или None. None рассматривается как пустой словарь. Это значение оборачивается вMappingProxyType(), чтобы сделать его неизменяемым, и оно доступно в объектеField. Оно вообще не используется dataclass и предоставляется как механизм расширения третьих сторон. Несколько сторонних организаций могут использовать каждый свой ключ в качестве пространства имен в метаданных. -
kw_only: Если истинно, это поле будет помечено как только для ключевых слов. Это используется при вычислении параметров сгенерированного метода__init__().
Новая версия 3.10.
Если значение по умолчанию для поля задано вызовом
field(), то атрибут класса для этого поля будет заменён на указанное значениеdefault. Если значениеdefaultне указано, то атрибут класса будет удален. Цель состоит в том, чтобы после выполнения декоратораdataclass()атрибуты класса содержали значения по умолчанию для полей так, как если бы само значение по умолчанию было указано. Например, после:@dataclass class C: x: int y: int = field(repr=False) z: int = field(repr=False, default=10) t: int = 20Атрибут класса
C.zбудет10, атрибут классаC.tбудет20, а атрибуты классовC.xиC.yне будут заданы. -
-
class dataclasses.Field -
Объекты
Fieldописывают каждое определенное поле. Эти объекты создаются внутри и возвращаются методом уровня модуляfields()(см. ниже). Пользователи никогда не должны напрямую создавать объектField. Его документированные атрибуты:-
name: Имя поля. -
type: Тип поля. -
default,default_factory,init,repr,hash,compare,metadata, иkw_onlyимеют идентичное значение и значения, как и в функцииfield().
Другие атрибуты могут существовать, но они являются приватными и их нельзя инспектировать или на них полагаться.
-
-
dataclasses.fields(class_or_instance) -
Возвращает кортеж объектов
Field, которые определяют поля для этого dataclass. Принимает либо dataclass, либо экземпляр dataclass. Поднимает исключениеTypeError, если не передано dataclass или экземпляр dataclass. Не возвращает псевдополя, которые являютсяClassVarилиInitVar.
-
dataclasses.asdict(obj, *, dict_factory=dict) -
Преобразует dataclass
objв словарь (используя функцию-фабрикуdict_factory). Каждый dataclass преобразуется в словарь своих полей, как парыname: value. dataclasses, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощьюcopy.deepcopy().Пример использования
asdict()для вложенных dataclass:@dataclass class Point: x: int y: int @dataclass class C: mylist: list[Point] p = Point(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Point(0, 0), Point(10, 4)]) assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}Для создания поверхностной копии можно использовать следующее решение:
dict((field.name, getattr(obj, field.name)) for field in fields(obj))
asdict()поднимаетTypeError, еслиobjне является экземпляром dataclass.
-
dataclasses.astuple(obj, *, tuple_factory=tuple) -
Преобразует датакласс
objв кортеж (используя функцию-фабрикуtuple_factory). Каждый датакласс преобразуется в кортеж значений его полей. dataclasses, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощьюcopy.deepcopy().Продолжая предыдущий пример:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
Для создания поверхностной копии можно использовать следующий обходной путь:
tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))
astuple()генерируетTypeError, еслиobjне является экземпляром датакласса.
-
dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) -
Создаёт новый датакласс с именем
cls_name, полями, определёнными вfields, базовыми классами, указанными вbases, и инициализированными в пространстве имён, заданном вnamespace.fields— это итерируемый объект, элементы которого являются либоname, либо(name, type), либо(name, type, Field). Если указан толькоname, тоtyping.Anyиспользуется дляtype. Значенияinit,repr,eq,order,unsafe_hash,frozen,match_args,kw_only,slots, иweakref_slotимеют такое же значение, как и вdataclass().Эта функция не строго необходима, поскольку любой механизм Python для создания нового класса с
__annotations__может затем применить функциюdataclass()для преобразования этого класса в датакласс. Эта функция предоставляется для удобства. Например:C = make_dataclass('C', [('x', int), 'y', ('z', int, field(default=5))], namespace={'add_one': lambda self: self.x + 1})Эквивалентно:
@dataclass class C: x: int y: 'typing.Any' z: int = 5 def add_one(self): return self.x + 1
-
dataclasses.replace(obj, /, **changes) -
Создаёт новый объект того же типа, что и
obj, заменяя поля значениями изchanges. Еслиobjне является датаклассом, генерируетсяTypeError. Если значения вchangesне задают поля, генерируетсяTypeError.Новый возвращаемый объект создаётся путём вызова метода
__init__()датакласса. Это гарантирует, что __post_init__, если он есть, также будет вызван.Переменные, предназначенные только для инициализации, без значений по умолчанию, если они существуют, должны быть указаны при вызове
replace(), чтобы они могли быть переданы в__init__()и __post_init__.Ошибка возникает, если
changesсодержит поля, определённые как имеющиеinit=False. В этом случае будет поднято исключениеValueError.Будьте осторожны с поведением полей
init=Falseпри вызовеreplace(). Они не копируются из исходного объекта, а инициализируются в __post_init__, если они инициализируются вообще. Ожидается, что поляinit=Falseбудут редко и обдуманно использоваться. Если они используются, разумно иметь альтернативные конструкторы классов или, возможно, пользовательский методreplace()(или с аналогичным именем), который обрабатывает копирование экземпляров.
-
dataclasses.is_dataclass(obj) -
Возвращает
True, если его параметр является датаклассом или экземпляром датакласса, в противном случае возвращаетFalse.Если вам нужно узнать, является ли класс экземпляром датакласса (а не датаклассом самим по себе), добавьте дополнительную проверку на
not isinstance(obj, type):def is_dataclass_instance(obj): return is_dataclass(obj) and not isinstance(obj, type)
-
dataclasses.MISSING -
Значение-сентинэл, обозначающее отсутствие значения по умолчанию или factory по умолчанию.
-
dataclasses.KW_ONLY -
Значение-сентинэл, используемое в качестве аннотации типа. Любые поля после псевдополя с типом
KW_ONLYотмечаются как поля, доступные только по ключевым словам. Обратите внимание, что псевдополе типаKW_ONLYв противном случае полностью игнорируется. Это включает и имя такого поля. По соглашению, имя_используется для поляKW_ONLY. Поля, доступные только по ключевым словам, обозначают параметры__init__(), которые должны быть указаны в качестве ключевых слов при создании экземпляра класса.В этом примере поля
yиzбудут помечены как поля, доступные только по ключевым словам:@dataclass class Point: x: float _: KW_ONLY y: float z: float p = Point(0, y=1.5, z=2.0)В одном датаклассе запрещено указывать более одного поля с типом
KW_ONLY.Новое в версии 3.10.
-
exception dataclasses.FrozenInstanceError -
Генерируется, когда неявно определённый
__setattr__()или__delattr__()вызывается на датаклассе, который был определён сfrozen=True. Это подклассAttributeError.
Обработка после инициализации
Сгенерированный код __init__() вызовет метод с именем __post_init__(), если __post_init__() определён в классе. Он обычно вызывается как self.__post_init__(). Однако, если определены какие-либо поля InitVar, они также будут переданы в __post_init__() в порядке их определения в классе. Если метод __init__() не генерируется, то __post_init__() не будет автоматически вызван.
Это позволяет, среди прочего, инициализировать значения полей, зависящие от одного или нескольких других полей. Например:
@dataclass
class C:
a: float
b: float
c: float = field(init=False)
def __post_init__(self):
self.c = self.a + self.b
Метод __init__(), сгенерированный dataclass(), не вызывает базовые методы __init__(). Если базовый класс имеет метод __init__(), который нужно вызвать, обычно это делается в методе __post_init__():
@dataclass
class Rectangle:
height: float
width: float
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
Однако, в общем случае, сгенерированные методы __init__() датакласса не обязательно вызывать, так как производный датакласс позаботится об инициализации всех полей любого базового класса, являющегося самим датаклассом.
См. раздел ниже о переменных только для инициализации, чтобы узнать, как передавать параметры в __post_init__(). Также см. предупреждение о том, как replace() обрабатывает поля init=False.
Переменные класса
Одно из немногих мест, где dataclass() фактически проверяет тип поля, — это определение, является ли поле переменной класса, как определено в PEP 526. Это делается путём проверки, является ли тип поля typing.ClassVar. Если поле является ClassVar, оно исключается из рассмотрения как поле и игнорируется механизмами датакласса. Такие псевдополя ClassVar не возвращаются функцией модуля fields().
Переменные, используемые только при инициализации
Ещё одно место, где dataclass() проверяет аннотацию типа, — это определение, является ли поле переменной, используемой только при инициализации. Это делается путём проверки, является ли тип поля типом dataclasses.InitVar. Если поле является InitVar, оно считается псевдополем, называемым полем, используемым только при инициализации. Поскольку это не истинное поле, оно не возвращается функцией модульного уровня fields(). Поля, используемые только при инициализации, добавляются в качестве параметров к сгенерированному методу __init__(), и передаются в необязательный метод __post_init__. В противном случае они не используются классами dataclasses.
Например, предположим, что поле будет инициализировано из базы данных, если значение не указано при создании класса:
@dataclass
class C:
i: int
j: int | None = None
database: InitVar[DatabaseType | None] = None
def __post_init__(self, database):
if self.j is None and database is not None:
self.j = database.lookup('j')
c = C(10, database=my_database)
В этом случае fields() вернёт объекты Field для i и j, но не для database.
Замороженные экземпляры
Создать действительно неизменяемые объекты Python невозможно. Однако, передав frozen=True декоратору dataclass(), можно эмулировать неизменяемость. В этом случае классы dataclasses добавят методы __setattr__() и __delattr__() в класс. Эти методы будут генерировать исключение FrozenInstanceError при вызове.
Использование frozen=True несёт небольшую производительность: метод __init__() не может использовать простое присваивание для инициализации полей и должен использовать object.__setattr__().
Наследование
Когда класс dataclass создаётся декоратором dataclass(), он просматривает все базовые классы класса в обратном порядке MRO (то есть, начиная с object) и для каждого найденного dataclass добавляет поля этого базового класса в упорядоченное отображение полей. После добавления всех полей базового класса он добавляет свои поля в упорядоченное отображение. Все сгенерированные методы будут использовать это объединённое, вычисленное упорядоченное отображение полей. Поскольку поля находятся в порядке вставки, производные классы переопределяют базовые классы. Пример:
@dataclass
class Base:
x: Any = 15.0
y: int = 0
@dataclass
class C(Base):
z: int = 10
x: int = 15
Окончательный список полей в порядке: x, y, z. Окончательный тип x — int, как указано в классе C.
Сгенерированный метод __init__() для C будет выглядеть так:
def __init__(self, x: int = 15, y: int = 0, z: int = 10):
Переупорядочение параметров только по ключевым словам в __init__()
После вычисления параметров, необходимых для __init__(), все параметры только по ключевым словам перемещаются после всех обычных (не только по ключевым словам) параметров. Это требование для реализации параметров только по ключевым словам в Python: они должны следовать за параметрами не только по ключевым словам.
В этом примере Base.y, Base.w, и D.t — поля только по ключевым словам, а Base.x и D.z — обычные поля:
@dataclass
class Base:
x: Any = 15.0
_: KW_ONLY
y: int = 0
w: int = 1
@dataclass
class D(Base):
z: int = 10
t: int = field(kw_only=True, default=0)
Сгенерированный метод __init__() для D будет выглядеть так:
def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):
Обратите внимание, что параметры переупорядочены по сравнению с их расположением в списке полей: параметры, полученные из обычных полей, следуют за параметрами, полученными из полей только по ключевым словам.
Относительный порядок параметров только по ключевым словам сохраняется в переупорядоченном списке параметров __init__().
Функции-фабрики по умолчанию
Если field() задаёт default_factory, она вызывается без аргументов, когда необходимо значение по умолчанию для поля. Например, для создания нового экземпляра списка используйте:
mylist: list = field(default_factory=list)
Если поле исключено из __init__() (используя init=False) и поле также задаёт default_factory, тогда функция-фабрика по умолчанию всегда вызывается из сгенерированной функции __init__(). Это происходит, потому что нет другого способа присвоить полю начальное значение.
Изменяемые значения по умолчанию
Python хранит значения по умолчанию для членов переменных в атрибутах класса. Рассмотрим этот пример, не используя dataclasses:
class C:
x = []
def add(self, element):
self.x.append(element)
o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x
Обратите внимание, что два экземпляра класса C используют одну и ту же переменную класса x, как и ожидалось.
Используя dataclasses, если этот код был бы допустим:
@dataclass
class D:
x: list = [] # This code raises ValueError
def add(self, element):
self.x += element
он сгенерировал бы код, похожий на:
class D:
x = []
def __init__(self, x=x):
self.x = x
def add(self, element):
self.x += element
assert D().x is D().x
Это вызывает ту же проблему, что и исходный пример с использованием класса C. То есть, два экземпляра класса D, которые не указывают значение для x при создании экземпляра класса, будут использовать одну и ту же копию x. Поскольку dataclasses используют обычное создание классов Python, они также демонстрируют это поведение. Нет общего способа для классов Data, чтобы обнаружить это условие. Вместо этого декоратор dataclass() сгенерирует исключение ValueError, если обнаружит параметр по умолчанию, который нельзя хешировать. Предполагается, что если значение нельзя хешировать, оно изменяемое. Это частичное решение, но оно защищает от многих распространённых ошибок.
Использование функций-фабрик по умолчанию — это способ создания новых экземпляров изменяемых типов в качестве значений по умолчанию для полей:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Изменено в версии 3.11: Вместо поиска и запрета объектов типа list, dict, или set, в качестве значений по умолчанию теперь запрещены нехешируемые объекты. Нехешируемость используется для приблизительного определения изменяемости.
Поля с типом дескриптора
Поля, которым присвоено значение объект дескриптора в качестве значения по умолчанию, имеют следующие особые свойства:
- Значение поля, переданное в метод
__init__класса dataclass, передаётся в метод__set__дескриптора, а не заменяет объект дескриптора. - Аналогично, при получении или установке поля вызывается метод
__get__или__set__дескриптора, а не возвращается или заменяется объект дескриптора. - Для определения, содержит ли поле значение по умолчанию,
dataclassesвызовет метод__get__дескриптора, используя форму доступа к классу (т.е.descriptor.__get__(obj=None, type=cls). Если дескриптор возвращает значение в этом случае, оно используется в качестве значения по умолчанию поля. С другой стороны, если дескриптор генерируетAttributeErrorв этой ситуации, поле не получит значение по умолчанию).
class IntConversionDescriptor:
def __init__(self, *, default):
self._default = default
def __set_name__(self, owner, name):
self._name = "_" + name
def __get__(self, obj, type):
if obj is None:
return self._default
return getattr(obj, self._name, self._default)
def __set__(self, obj, value):
setattr(obj, self._name, int(value))
@dataclass
class InventoryItem:
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
i = InventoryItem()
print(i.quantity_on_hand) # 100
i.quantity_on_hand = 2.5 # calls __set__ with 2.5
print(i.quantity_on_hand) # 2
Обратите внимание, что если поле аннотировано типом дескриптора, но не получает объект дескриптора в качестве значения по умолчанию, оно ведёт себя как обычное поле.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/dataclasses.html