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добавит в класс различные методы «dunder», описанные ниже. Если какой-либо из добавляемых методов уже существует в классе, поведение зависит от параметра, как описано ниже. Декоратор возвращает тот же класс, к которому он применяется; новый класс не создаётся.Если
@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__(), этот параметр игнорируется.Изменено в версии 3.13: Теперь сгенерированный метод
__eq__сравнивает каждое поле отдельно (например,self.a == other.a and self.b == other.b), а не сравнивает кортежи полей, как в предыдущих версиях.Это изменение ускоряет сравнение, но может изменить результаты в случаях, когда атрибуты равны по идентичности, но не по значению (например,
float('nan')).В Python 3.12 и более ранних версиях сравнение выполнялось путём создания кортежей полей и их сравнения (например,
(self.a, self.b) == (other.a, other.b)). -
order: если значение равно true (по умолчанию —
False), будут сгенерированы методы__lt__(),__le__(),__gt__()и__ge__(). Они сравнивают класс так, как если бы он был кортежем своих полей, расположенных по порядку. Сравниваемые экземпляры должны иметь идентичный тип. Если order равно true, а eq — false, возникает исключениеValueError.Если класс уже определяет какой-либо из методов
__lt__(),__le__(),__gt__()или__ge__(), возникает исключениеTypeError. -
unsafe_hash: если значение равно true, принудительно заставляет
dataclassesсоздать метод__hash__(), даже если это может быть небезопасно. В противном случае метод__hash__()генерируется в зависимости от значений eq и frozen. Значение по умолчанию —False.__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__(), а frozen равно true, возникает исключениеTypeError. -
match_args: если значение равно true (по умолчанию —
True), кортеж__match_args__будет создан из списка параметров, не являющихся параметрами только по ключевому слову, сгенерированного метода__init__()(даже если__init__()не генерируется; см. выше). Если значение равно false или если__match_args__уже определён в классе,__match_args__не будет сгенерирован.
Добавлено в версии 3.10.
-
kw_only: если значение равно true (значение по умолчанию —
False), все поля будут помечены как параметры только по ключевому слову. Если поле помечено как параметр только по ключевому слову, это означает, что параметр__init__(), сгенерированный для такого поля, необходимо указывать по ключевому слову при вызове__init__(). Подробности см. в глоссарии в статье параметр. Также см. разделKW_ONLY.Поля, доступные только по ключевому слову, не включаются в
__match_args__.
Добавлено в версии 3.10.
-
slots: если значение равно true (по умолчанию —
False), будет сгенерирован атрибут__slots__, а вместо исходного будет возвращён новый класс. Если__slots__уже определён в классе, возникает исключениеTypeError.
Предупреждение
Передача параметров в
__init_subclass__()базового класса при использованииslots=Trueприведёт к исключениюTypeError. В качестве обходного решения используйте__init_subclass__без параметров или параметры со значениями по умолчанию. Подробности см. в gh-91126.Добавлено в версии 3.10.
Изменено в версии 3.11: Если имя поля уже включено в
__slots__базового класса, оно не будет включено в сгенерированный__slots__, чтобы не переопределять их. Поэтому не используйте__slots__для получения имён полей класса данных. Вместо этого используйтеfields(). Чтобы определить унаследованные слоты,__slots__базового класса может быть любым итерируемым объектом, но не итератором.-
weakref_slot: если значение равно true (по умолчанию —
False), добавляется слот с именем «__weakref__», необходимый, чтобы сделать экземплярweakref-able. Указание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, doc=None) -
Для распространённых и простых сценариев использования дополнительные возможности не нужны. Однако некоторые функции классов данных требуют дополнительной информации для каждого поля. Чтобы предоставить эту дополнительную информацию, можно заменить значение поля по умолчанию вызовом предоставленной функции
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: это значение может быть логическим или
None. Если значение равно true, поле включается в сгенерированный метод__hash__(). Если значение равно false, поле исключается из сгенерированного__hash__(). Если указаноNone(значение по умолчанию), используется значение compare: обычно это ожидаемое поведение, поскольку поле следует включать в хеш, если оно используется при сравнении. Не рекомендуется задавать этому параметру значение, отличное отNone.Одной из возможных причин указать
hash=False, ноcompare=True, может быть высокая вычислительная стоимость хеширования поля, необходимого для проверки равенства, если при этом имеются другие поля, участвующие в вычислении хеша типа. Даже если поле исключено из хеша, оно всё равно будет использоваться при сравнении. -
compare: если значение равно true (по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (
__eq__(),__gt__()и т. д.). -
metadata: это значение может быть отображением или
None.Noneрассматривается как пустой словарь. Это значение оборачивается вMappingProxyType(), чтобы сделать его доступным только для чтения, и предоставляется через объектField. Классы данных не используют его; оно предоставлено как механизм расширения для сторонних разработчиков. У каждой сторонней библиотеки может быть собственный ключ, используемый в качестве пространства имён в метаданных. -
kw_only: если значение равно true, поле будет помечено как параметр только по ключевому слову. Этот параметр используется при вычислении параметров сгенерированного метода
__init__().Поля, доступные только по ключевому слову, также не включаются в
__match_args__.
Добавлено в версии 3.10.
- doc: необязательная строка документации для этого поля.
Добавлено в версии 3.14.
Если значение поля по умолчанию задано вызовом
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не будут заданы. -
default: если указано, это будет значение поля по умолчанию. Оно необходимо, поскольку сам вызов
-
class dataclasses.Field -
Объекты
Fieldописывают каждое определённое поле. Эти объекты создаются внутренними средствами и возвращаются методом уровня модуляfields()(см. ниже). Пользователям не следует создавать объектыFieldнапрямую. Документированные атрибуты:-
name: имя поля. -
type: тип поля. -
default,default_factory,init,repr,hash,compare,metadataиkw_onlyимеют такое же значение и смысл, как в функцииfield().
Могут существовать и другие атрибуты, но они являются закрытыми, поэтому их не следует проверять или использовать.
-
-
class dataclasses.InitVar -
Аннотации типа
InitVar[T]описывают переменные, используемые только при инициализации. Поля с аннотациейInitVarсчитаются псевдополями, поэтому функцияfields()их не возвращает, и они никак не используются, кроме как добавления в качестве параметров в__init__()и, при необходимости, в__post_init__().
-
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}]}Для создания поверхностной копии можно использовать следующий обходной путь:
{field.name: getattr(obj, field.name) for field in fields(obj)}asdict()вызывает исключениеTypeError, если obj не является экземпляром класса данных.
-
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))
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, module=None, decorator=dataclass) -
Создаёт новый класс данных с именем 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.Если задан module, атрибут
__module__класса данных получает это значение. По умолчанию ему присваивается имя модуля вызывающего кода.Параметр decorator — это вызываемый объект, который будет использоваться для создания класса данных. Он должен принимать объект класса в качестве первого аргумента и те же именованные аргументы, что и
@dataclass. По умолчанию используется функция@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Добавлено в версии 3.14: Добавлен параметр decorator.
-
dataclasses.replace(obj, /, **changes) -
Создаёт новый объект того же типа, что и obj, заменяя поля значениями из changes. Если obj не является классом данных, возникает исключение
TypeError. Если ключи в changes не являются именами полей указанного класса данных, возникаетTypeError.Новый возвращаемый объект создаётся вызовом метода
__init__()класса данных. Это гарантирует вызов__post_init__(), если он имеется.Если существуют переменные, используемые только при инициализации и не имеющие значений по умолчанию, их необходимо указать при вызове
replace(), чтобы их можно было передать в__init__()и__post_init__().Поля, определённые как
init=False, не должны содержаться в changes. В этом случае возникает исключениеValueError.Обратите внимание на работу полей
init=Falseпри вызовеreplace(). Они не копируются из исходного объекта, а инициализируются в__post_init__(), если вообще инициализируются. Предполагается, что поляinit=Falseбудут использоваться редко и осмотрительно. Если они используются, может быть разумно предусмотреть альтернативные конструкторы класса или, возможно, пользовательский методreplace()(или метод с похожим именем), который выполняет копирование экземпляров.Экземпляры классов данных также поддерживаются универсальной функцией
copy.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 -
Значение-маркер, обозначающее отсутствие значения по умолчанию или фабрики значений по умолчанию.
-
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.
Обработка после инициализации
-
dataclasses.__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.
Переменные класса
Одно из немногих мест, где @dataclass действительно проверяет тип поля, — определение того, является ли поле переменной класса, как определено в PEP 526. Для этого проверяется, является ли тип поля typing.ClassVar. Если поле является ClassVar, оно не считается полем и игнорируется механизмами dataclass. Такие псевдополя ClassVar не возвращаются функцией уровня модуля fields().
Переменные, используемые только при инициализации
Ещё одно место, где @dataclass проверяет аннотацию типа, — определение того, является ли поле переменной, используемой только при инициализации. Для этого проверяется, имеет ли тип поля тип InitVar. Если поле является InitVar, оно считается псевдополем, называемым полем, используемым только при инициализации. Поскольку это не настоящее поле, оно не возвращается функцией уровня модуля fields(). Такие поля добавляются в качестве параметров в сгенерированный метод __init__() и передаются в необязательный метод __post_init__(). В остальном dataclass их не использует.
Например, предположим, что поле инициализируется из базы данных, если при создании класса значение не задано:
@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, можно имитировать неизменяемость. В этом случае dataclass добавит в класс методы __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 хранит значения переменных-членов по умолчанию в атрибутах класса. Рассмотрим следующий пример без использования dataclass:
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, как и ожидалось.
Если бы следующий код с использованием dataclass был допустим:
@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. Поскольку dataclass используют обычное создание классов в Python, им присуще такое же поведение. Не существует общего способа, позволяющего Data Classes обнаружить эту ситуацию. Вместо этого декоратор @dataclass возбуждает исключение ValueError, если обнаруживает нехешируемый параметр по умолчанию. Предполагается, что нехешируемое значение является изменяемым. Это решение не охватывает все случаи, но позволяет предотвратить многие распространённые ошибки.
Функции-фабрики значений по умолчанию позволяют создавать новые экземпляры изменяемых типов в качестве значений полей по умолчанию:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Поля с типом дескриптора
Для полей, которым в качестве значения по умолчанию назначены объекты-дескрипторы, действуют следующие особые правила:
- Значение поля, передаваемое методу
__init__()dataclass, передаётся методу__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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/dataclasses.html