Содержание страницы
dataclasses – Классы данных¶dataclasses – Data Classes
Исходный код: Lib/dataclasses.py
Этот модуль предоставляет декоратор и функции для автоматического добавления генерируемых специальных методов, таких как __init__() и __repr__(), в пользовательские классы. Изначально он описан в PEP 557.
Переменные-члены, используемые в этих сгенерированных методах, определяются с помощью аннотаций типов PEP 526. Например, следующий код:
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Класс для отслеживания предмета в инвентаре."""
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.
Содержимое модуля¶Module contents
- @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__()в классе данных и устанавливать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: Если true (значение по умолчаниюFalse), то все поля будут помечены как keyword-only. Если поле помечено как keyword-only, то единственный эффект заключается в том, что параметр__init__(), сгенерированный из такого поля, должен быть указан с ключевым словом при вызове__init__(). Это не влияет ни на какие другие аспекты dataclasses. Подробнее см. запись глоссария параметр. Также см. разделKW_ONLY.
Новое в версии 3.10.
slots: Если true (по умолчаниюFalse), будет сгенерирован атрибут__slots__и возвращён новый класс вместо исходного. Если__slots__уже определён в классе, то возбуждаетсяTypeError.
Новое в версии 3.10.
Изменено в версии 3.11: Если имя поля уже присутствует в
__slots__базового класса, оно не будет включено в сгенерированный__slots__, чтобы избежать их переопределения. Поэтому не используйте__slots__для получения имён полей dataclass. Используйтеfields(). Чтобы можно было определить унаследованные слоты,__slots__базового класса может быть любым итерируемым объектом, но не итератором.weakref_slot: Если true (по умолчаниюFalse), добавляет слот с именем «__weakref__», который необходим для создания слабых ссылок на экземпляр. Указыватьweakref_slot=Trueбезslots=True– ошибка.
Новое в версии 3.11.
fieldмогут опционально задавать значение по умолчанию, используя обычный синтаксис Python:@dataclass class C: a: int # 'a' не имеет значения по умолчанию b: int = 0 # задайте значение по умолчанию для '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. Оно вообще не используется Data Classes и предоставляется как механизм расширения сторонними библиотеками. Несколько сторонних библиотек могут иметь свои собственные ключи для использования в качестве пространства имён в метаданных.kw_only: Если true, это поле будет помечено как keyword-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, определяющих поля этого датакласса. Принимает либо датакласс, либо экземпляр датакласса. ВызываетTypeError, если передан не датакласс и не его экземпляр. Не возвращает псевдополя, которые являютсяClassVarилиInitVar.
- dataclasses.asdict(obj, *, dict_factory=dict)¶
Преобразует dataclass
objв словарь (с помощью фабричной функцииdict_factory). Каждый dataclass преобразуется в словарь своих полей в виде парname: value. Рекурсивно обрабатываются dataclass'ы, словари, списки и кортежи. Другие объекты копируются с помощьюcopy.deepcopy().Пример использования
asdict()на вложенных датаклассах:@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 преобразуется в кортеж значений своих полей. Рекурсивно обрабатываются dataclass'ы, словари, списки и кортежи. Другие объекты копируются с помощью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, weakref_slot=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и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не является Data Class, возбуждаетTypeError. Если значения вchangesне указывают поля, возбуждаетTypeError.Вновь возвращаемый объект создаётся вызовом метода
__init__()dataclass. Это гарантирует, что __post_init__, если он присутствует, также вызывается.Переменные, предназначенные только для инициализации, без значений по умолчанию (если таковые имеются) должны быть указаны в вызове
replace(), чтобы их можно было передать в__init__()и__post_init__().Ошибкой считается, если
changesсодержит какие-либо поля, определённые как имеющиеinit=False. В этом случае будет возбужденоValueError.Следует заранее знать, как работают поля
init=Falseпри вызовеreplace(). Они не копируются из исходного объекта, а инициализируются в__post_init__(), если вообще инициализируются. Ожидается, что поляinit=Falseбудут использоваться редко и осмотрительно. Если они используются, возможно, разумно иметь альтернативные конструкторы класса или, возможно, пользовательский методreplace()(или с похожим именем), который обрабатывает копирование экземпляра.
- dataclasses.is_dataclass(obj)¶
Возвращает
True, если его параметр является dataclass или экземпляром dataclass, иначе возвращаетFalse.Если нужно узнать, является ли класс экземпляром dataclass (а не самим dataclass), добавьте дополнительную проверку на
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помечаются как keyword-only поля. Обратите внимание, что псевдополе типаKW_ONLYв остальном полностью игнорируется, включая имя такого поля. По соглашению имя_используется для поляKW_ONLY. Keyword-only поля означают параметры__init__(), которые должны быть указаны как ключевые слова при создании класса.В этом примере поля
yиzбудут помечены как keyword-only поля:@dataclass class Point: x: float _: KW_ONLY y: float z: float p = Point(0, y=1.5, z=2.0)
В одном dataclass указывать более одного поля с типом
KW_ONLY– ошибка.Новое в версии 3.10.
- exception dataclasses.FrozenInstanceError¶
Возбуждается, когда неявно определённые
__setattr__()или__delattr__()вызываются для dataclass, определённого сfrozen=True. Является подклассомAttributeError.
Обработка после инициализации¶Post-init processing
Сгенерированный код __init__() вызовет метод с именем __post_init__(), если __post_init__() определён в классе. Обычно он вызывается как self.__post_init__(). Однако, если определены какие-либо поля InitVar, они также будут переданы в __post_init__() в порядке их определения в классе. Если метод __init__() не сгенерирован, то __post_init__() не будет вызываться автоматически.
Если метод определён в классе, он будет вызван сгенерированным
__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__():
class Rectangle:
def __init__(self, height, width):
self.height = height
self.width = width
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
Однако обратите внимание, что в целом вызывать сгенерированные dataclass методы __init__() не требуется, поскольку производный dataclass сам позаботится об инициализации всех полей любого базового класса, который сам является dataclass.
Смотрите раздел ниже о переменных, используемых только для инициализации, чтобы узнать о способах передачи параметров в __post_init__(). Также обратите внимание на предупреждение о том, как replace() обрабатывает поля init=False.
Переменные класса¶Class variables
Одно из немногих мест, где @dataclass фактически проверяет тип поля, – это определение, является ли поле переменной класса, как определено в PEP 526. Это делается путём проверки, является ли тип поля typing.ClassVar. Если поле является ClassVar, оно исключается из рассмотрения как поле и игнорируется механизмами dataclass. Такие псевдополя ClassVar не возвращаются функцией модульного уровня fields().
Переменные только для инициализации¶Init-only variables
Ещё одно место, где @dataclass проверяет аннотации типов, – это определение, является ли поле переменной, используемой только при инициализации. Это делается путём проверки, является ли тип поля типом dataclasses.InitVar. Если поле является InitVar, оно считается псевдополем, называемым init-only полем. Поскольку это не настоящее поле, оно не возвращается функцией уровня модуля fields(). Поля init-only добавляются в качестве параметров в сгенерированный метод __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.
Замороженные экземпляры¶Frozen instances
Создать по-настоящему неизменяемые объекты Python невозможно. Однако, передав frozen=True декоратору @dataclass, можно эмулировать неизменяемость. В этом случае dataclasses добавит в класс методы __setattr__() и __delattr__(). При вызове эти методы вызовут исключение FrozenInstanceError.
Использование frozen=True сопряжено с небольшим снижением производительности: __init__() не может использовать простое присваивание для инициализации полей и должен использовать __setattr__().
Наследование¶Inheritance
Когда 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):
Переупорядочивание keyword-only параметров в __init__()¶Re-ordering of keyword-only parameters in __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__().
Функции фабрики по умолчанию¶Default factory functions
Если field() указывает default_factory, то он вызывается без аргументов, когда требуется значение по умолчанию для поля. Например, чтобы создать новый экземпляр списка, используйте:
mylist: list = field(default_factory=list)
Если поле исключено из __init__() (с помощью init=False) и поле также указывает default_factory, то функция фабрики значений по умолчанию всегда будет вызываться из сгенерированной функции __init__(). Это происходит потому, что нет другого способа задать полю начальное значение.
Изменяемые значения по умолчанию¶Mutable default values
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 = [] # Этот код вызывает ValueError
def add(self, element):
self.x.append(element)
он бы сгенерировал код, похожий на:
class D:
x = []
def __init__(self, x=x):
self.x = x
def add(self, element):
self.x.append(element)
assert D().x is D().x
Это та же проблема, что и в исходном примере с классом C.
То есть два экземпляра класса D, которые не указывают значение
для x при создании экземпляра класса, будут использовать одну и ту же копию
x. Поскольку dataclasses просто используют обычное создание классов Python, они также разделяют это поведение. Не существует общего способа для Data Classes обнаружить эту ситуацию. Вместо этого
декоратор @dataclass вызовет исключение ValueError, если обнаружит нехешируемый параметр по умолчанию. Предполагается, что если значение нехешируемо, то оно изменяемо. Это частичное решение,
но оно защищает от многих распространённых ошибок.
Использование фабричных функций по умолчанию – это способ создавать новые экземпляры изменяемых типов в качестве значений полей по умолчанию:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Изменено в версии 3.11: Вместо поиска и запрета объектов типа list,
dict или set, теперь нехешируемые объекты не допускаются в качестве
значений по умолчанию. Нехешируемость используется для приблизительной оценки
изменяемости.
Поля с типом дескриптора¶Descriptor-typed fields
Поля, которым назначены объекты-дескрипторы в качестве значения по умолчанию, обладают следующим особым поведением:
Значение поля, переданное в метод
__init__()класса данных, передается в метод__set__()дескриптора, а не перезаписывает объект-дескриптор.Аналогично, при чтении или записи поля вызывается метод
__get__()или__set__()дескриптора, а не возвращается или перезаписывается объект-дескриптор.Чтобы определить, содержит ли поле значение по умолчанию,
@dataclassвызовет метод__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 # вызывает __set__ с 2.5
print(i.quantity_on_hand) # 2
Обратите внимание: если поле аннотировано типом дескриптора, но ему не назначен объект-дескриптор в качестве значения по умолчанию, такое поле будет вести себя как обычное поле.