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: Если значение истинно (по умолчанию), будет сгенерирован метод__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равно 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, это означает переход к хешированию на основе идентификатора). -
frozen: Если значение истинно (по умолчанию оно равноFalse), присваивание значений полям вызовет исключение. Это имитирует замороженные экземпляры, доступные только для чтения. Если в классе определён__setattr__()или__delattr__(), возникает исключениеTypeError. См. обсуждение ниже. -
match_args: Если значение истинно (по умолчанию оно равноTrue), кортеж__match_args__будет создан из списка параметров сгенерированного метода__init__()(даже если__init__()не генерируется; см. выше). Если значение ложно или__match_args__уже определён в классе,__match_args__не будет сгенерирован.
Добавлено в версии 3.10.
-
kw_only: Если значение истинно (по умолчанию оно равноFalse), все поля будут помечены как доступные только по ключевому слову. Если поле помечено как доступное только по ключевому слову, это влияет только на то, что параметр__init__(), сгенерированный для такого поля, при вызове__init__()необходимо указывать по ключевому слову. На другие аспекты классов данных это не влияет. Подробности см. в глоссарной статье о параметрах. См. также разделKW_ONLY.
Добавлено в версии 3.10.
-
slots: Если значение истинно (по умолчанию оно равноFalse), будет сгенерирован атрибут__slots__, а вместо исходного класса будет возвращён новый. Если__slots__уже определён в классе, возникает исключениеTypeError.
Добавлено в версии 3.10.
Изменено в версии 3.11: Если имя поля уже включено в
__slots__базового класса, оно не будет включено в сгенерированный__slots__, чтобы предотвратить переопределение этих элементов. Поэтому не используйте__slots__для получения имён полей класса данных. Вместо этого используйте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) -
Для распространённых простых сценариев использования дополнительные возможности не требуются. Однако некоторые возможности классов данных требуют дополнительных сведений для каждого поля. Чтобы предоставить эти сведения, можно заменить значение поля по умолчанию вызовом предоставленной функции
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. Оно никак не используется классами данных и предназначено для расширения сторонними разработчиками. У разных сторонних разработчиков могут быть собственные ключи, используемые в качестве пространства имён в метаданных. -
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, определяющих поля этого класса данных. Принимает класс данных или его экземпляр. Если передан не класс данных и не его экземпляр, возникает исключениеTypeError. Не возвращает псевдополя, которыми являютсяClassVarилиInitVar.
-
dataclasses.asdict(obj, *, dict_factory=dict) -
Преобразует экземпляр класса данных
objв словарь (с помощью фабричной функцииdict_factory). Каждый класс данных преобразуется в словарь своих полей в виде парname: value. Рекурсивно обрабатываются классы данных, словари, списки и кортежи. Остальные объекты копируются с помощью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))
Если
objне является экземпляром класса данных,asdict()вызывает исключениеTypeError.
-
dataclasses.astuple(obj, *, tuple_factory=tuple) -
Преобразует экземпляр класса данных
objв кортеж (с помощью фабричной функцииtuple_factory). Каждый класс данных преобразуется в кортеж значений своих полей. Рекурсивно обрабатываются классы данных, словари, списки и кортежи. Остальные объекты копируются с помощью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))
Если
objне является экземпляром класса данных,astuple()вызывает исключениеTypeError.
-
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, дляtypeиспользуетсяtyping.Any. Значения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 -
Значение-сентинел, обозначающее отсутствие значения по умолчанию или 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.Добавлено в версии 3.10.
-
exception dataclasses.FrozenInstanceError -
Возникает при вызове неявно определённого
__setattr__()или__delattr__()для класса данных, определённого с параметромfrozen=True. Является подклассомAttributeError.
Обработка после инициализации
Сгенерированный код __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)
Однако следует учитывать, что обычно вызывать сгенерированные для класса данных методы __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__() не может инициализировать поля простым присваиванием и должен использовать __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() указана 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.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, такое поведение сохраняется и здесь. Не существует общего способа, с помощью которого классы данных могли бы обнаружить эту ситуацию. Вместо этого декоратор @dataclass вызовет исключение ValueError, если обнаружит нехешируемый параметр по умолчанию. Предполагается, что нехешируемое значение является изменяемым. Это решение не охватывает все случаи, но помогает избежать многих распространённых ошибок.
Фабричные функции значений по умолчанию позволяют создавать новые экземпляры изменяемых типов в качестве значений полей по умолчанию:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Изменено в версии 3.11: Вместо поиска объектов типа list, dict или set и их запрета теперь запрещены нехешируемые объекты в качестве значений по умолчанию. Нехешируемость используется как приближённый признак изменяемости.
Поля с типом-дескриптором
Для полей, которым в качестве значения по умолчанию присваиваются объекты-дескрипторы, действуют следующие особые правила:
- Значение поля, переданное методу
__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 # calls __set__ with 2.5
print(i.quantity_on_hand) # 2
Обратите внимание: если поле аннотировано типом-дескриптором, но в качестве значения по умолчанию ему не присвоен объект-дескриптор, оно будет вести себя как обычное поле.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/dataclasses.html