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) -
Эта функция является декоратором, который используется для добавления сгенерированных специальных методов к классам, как описано ниже.
Декоратор
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) class C: ...Параметры для
dataclass():-
init: Если истинно (по умолчанию), будет сгенерирован метод__init__().Если класс уже определяет
__init__(), этот параметр игнорируется. -
repr: Если истинно (по умолчанию), будет сгенерирован метод__repr__(). Сгенерированная строка repr будет содержать имя класса и имя и repr каждого поля в порядке их определения в классе. Поля, помеченные как исключенные из repr, не включаются. Например:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).Если класс уже определяет
__repr__(), этот параметр игнорируется. -
eq: Если истинно (по умолчанию), будет сгенерирован метод__eq__(). Этот метод сравнивает класс, как если бы он был кортежем его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа.Если класс уже определяет
__eq__(), этот параметр игнорируется. -
order: Если истинно (по умолчаниюFalse), методы__lt__(),__le__(),__gt__(), и__ge__()будут сгенерированы. Эти методы сравнивают класс, как если бы он был кортежем его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа. Еслиorderистинно, аeqложно, возникает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оба истинные, по умолчаниюdataclass()сгенерирует метод__hash__()для вас. Еслиeqистинно, аfrozenложно,__hash__()будет установлено вNone, помечая его как неизменяемый (что оно и есть, так как оно изменяемо). Еслиeqложно,__hash__()останется неизменным, что означает, что метод__hash__()суперкласса будет использован (если суперклассobject, это означает, что хэширование вернётся к хэшированию на основе id). -
frozen: Если истинно (по умолчаниюFalse), присваивание полям будет генерировать исключение. Это имитирует неизменяемые экземпляры только для чтения. Если__setattr__()или__delattr__()определены в классе, то возникаетTypeError. См. обсуждение ниже. -
match_args: Если истинно (по умолчаниюTrue), кортеж__match_args__будет создан из списка параметров сгенерированного метода__init__()(даже если__init__()не сгенерирован, см. выше). Если ложно, или если__match_args__уже определен в классе, то__match_args__не будет сгенерирован.
Введено в версии 3.10.
-
kw_only: Если истинно (значение по умолчаниюFalse), все поля будут помечены как только для ключевых слов. Если поле помечено как только для ключевых слов, единственное влияние заключается в том, что параметр__init__(), сгенерированный из поля только для ключевых слов, должен быть указан с ключевым словом при вызове__init__(). На другие аспекты dataclasses это никак не влияет. Для получения подробностей см. запись в глоссарии параметр. Также см. разделKW_ONLY.
Введено в версии 3.10.
-
slots: Если истинно (по умолчаниюFalse), атрибут__slots__будет сгенерирован и новый класс будет возвращён вместо исходного. Если__slots__уже определён в классе, то возникаетTypeError.
Введено в версии 3.10.
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: Если True (значение по умолчанию), это поле включено в качестве параметра для сгенерированного метода__init__(). -
repr: Если True (значение по умолчанию), это поле включено в строку, возвращаемую сгенерированным методом__repr__(). -
hash: Это может быть bool илиNone. Если True, это поле включено в сгенерированный метод__hash__(). ЕслиNone(значение по умолчанию), используется значениеcompare: это обычно ожидаемое поведение. Поле должно учитываться в хэше, если оно используется для сравнения. Не рекомендуется устанавливать это значение на что-либо отличное отNone.Одна из возможных причин установить
hash=Falseноcompare=Trueможет быть в том, что поле дорогостояще для вычисления значения хэша, это поле необходимо для проверки на равенство, и есть другие поля, которые вносят вклад в значение хэша типа. Даже если поле исключено из хэша, оно по-прежнему будет использоваться для сравнения. -
compare: Если True (значение по умолчанию), это поле включено в сгенерированные методы сравнения и равенства (__eq__(),__gt__(), и т. д.). -
metadata: Это может быть словарь или None. None обрабатывается как пустой словарь. Это значение обернуто вMappingProxyType()для повышения его неизменяемости и показано в объектеField. Он вообще не используется классами данных и предоставляется в качестве механизма расширения сторонними разработчиками. Несколько сторонних разработчиков могут иметь свои собственные ключи для использования в качестве пространства имён в метаданных. -
kw_only: Если True, это поле будет помечено как только для ключевых слов. Это используется при вычислении параметров сгенерированного метода__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()на вложенных dataclasses:@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) -
Преобразует dataclass
objв кортеж (используя фабричную функциюtuple_factory). Каждый dataclass преобразуется в кортеж его значений полей. 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не является экземпляром dataclass.
-
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) -
Создаёт новый dataclass с именем
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имеют то же значение, что и вdataclass().Эта функция не строго необходима, потому что любой механизм Python для создания нового класса с
__annotations__может применить функциюdataclass()для преобразования этого класса в 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 -
Значение-сентинель, обозначающее отсутствие значения по умолчанию или default_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.New in version 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__() . Они не используются иначе классами данных.
Например, предположим, что поле будет инициализироваться из базы данных, если значение не указано при создании класса:
@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(), можно смоделировать неизменяемость. В этом случае классы данных добавят методы __setattr__() и __delattr__() в класс. Эти методы будут генерировать исключение FrozenInstanceError при вызове.
Использование frozen=True влечёт за собой незначительную потерю производительности: __init__() не может использовать простое присваивание для инициализации полей и должен использовать object.__setattr__().
Наследование
Когда класс данных создаётся с помощью декоратора dataclass(), он просматривает все базовые классы класса в обратном порядке MRO (т.е., начиная с object) и для каждого класса данных, который находит, добавляет поля из этого базового класса в упорядоченное отображение полей. После добавления всех полей базовых классов он добавляет свои собственные поля в упорядоченное отображение. Все сгенерированные методы будут использовать это комбинированное упорядоченное отображение полей. Поскольку поля упорядочены в порядке вставки, производные классы переопределяют базовые классы. Пример:
@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() указана функция создания объекта по умолчанию, она вызывается без аргументов, когда требуется значение по умолчанию для поля. Например, для создания нового списка используйте:
mylist: list = field(default_factory=list)
Если поле исключено из __init__() (используя init=False) и также указана функция создания объекта по умолчанию, то эта функция всегда будет вызываться из сгенерированной функции __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 = []
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 Classes обнаружить это условие. Вместо этого декоратор dataclass() будет поднимать исключение TypeError, если обнаружит параметр по умолчанию типа list, dict, или set. Это частичное решение, но оно защищает от многих распространённых ошибок.
Использование функций создания объектов по умолчанию — это способ создания новых экземпляров изменяемых типов в качестве значений по умолчанию для полей:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Поля с типами-дескрипторами
Для полей, которым назначены объекты-дескрипторы в качестве значения по умолчанию, характерно следующее поведение:
- Значение для поля, передаваемое методу
__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.10/library/dataclasses.html