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 истинно, а 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.
Предупреждение
Вызов безаргументного
super()в dataclasses с использованиемslots=Trueприведёт к тому, что будет выброшено следующее исключение:TypeError: super(type, obj): obj must be an instance or subtype of type. Двухаргументныйsuper()— это допустимое решение. См. gh-90562 для получения полной информации.Предупреждение
Передача параметров базовому классу
__init_subclass__()при использованииslots=Trueприведёт к исключениюTypeError. Либо используйте__init_subclass__без параметров, либо используйте значения по умолчанию в качестве обходного пути. См. gh-91126 для получения полной информации.Добавлен в версии 3.10.
Изменено в версии 3.11: Если имя поля уже включено в
__slots__базового класса, оно не будет включено в сгенерированный__slots__для предотвращения их перезаписи. Поэтому не используйте__slots__для получения имён полей dataclass. Используйте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будет поднят, если поле без значения по умолчанию следует за полем с значением по умолчанию. Это справедливо, как для одного класса, так и в результате наследования классов.-
weakref_slot: Если значение равно true (по умолчанию
-
dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING) -
Для общих и простых случаев использования, другой функциональности не требуется. Однако некоторые возможности Data Classes требуют дополнительной информации для каждого поля. Чтобы удовлетворить эту потребность в дополнительной информации, вы можете заменить значение поля по умолчанию вызовом предоставленной функции
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, это поле будет помечено как только для ключевых аргументов. Это используется при вычислении параметров генерируемого метода
__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не будут установлены. -
default: Если предоставлено, это будет значение по умолчанию для данного поля. Это необходимо, потому что сам вызов
-
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, которые определяют поля для этой Data Class. Принимает либо Data Class, либо экземпляр Data Class. ПоднимаетTypeError, если не передано Data Class или экземпляр Data Class. Не возвращает псевдо-поля, которые являютсяClassVarилиInitVar.
-
dataclasses.asdict(obj, *, dict_factory=dict) -
Преобразует Data Class obj в словарь (используя фабричную функцию dict_factory). Каждая Data Class преобразуется в словарь её полей, как пары
name: value. Data Classes, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощьюcopy.deepcopy().Пример использования
asdict()на вложенных Data Classes:@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 не является экземпляром Data Class.
-
dataclasses.astuple(obj, *, tuple_factory=tuple) -
Преобразует Data Class obj в кортеж (используя фабричную функцию tuple_factory). Каждая Data Class преобразуется в кортеж значений её полей. Data Classes, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощью
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 не является экземпляром Data Class.
-
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) -
Создаёт новую Data Class с именем 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__Data Class устанавливается в это значение. По умолчанию он устанавливается в имя модуля вызывающей функции.Эта функция не строго необходима, потому что любой механизм Python для создания нового класса с
__annotations__может затем применить функцию@dataclassдля преобразования этого класса в Data Class. Эта функция предоставляется для удобства. Например: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()(или аналогичное имя), который обрабатывает копирование экземпляров.Экземпляры классов данных также поддерживаются универсальной функцией
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 -
Значение-маяк, обозначающее отсутствие значения по умолчанию или 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.
Обработка после инициализации
-
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)
Однако в общем случае сгенерированные методами класса данных методы __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() указывает 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, они также демонстрируют такое поведение. Нет общего способа для Data Classes обнаружить это условие. Вместо этого декоратор @dataclass вызовет ValueError, если он обнаружит параметр по умолчанию, который нельзя хешировать. Предполагается, что если значение не хешируется, оно изменяемо. Это частичное решение, но оно защищает от многих распространенных ошибок.
Использование функций по умолчанию — это способ создания новых экземпляров изменяемых типов в качестве значений по умолчанию для полей:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
Поля с типом-дескриптор
Поля, которым присвоено значение объекты-дескрипторы в качестве значения по умолчанию, имеют следующие особенности:
- Значение для поля, переданное методу
__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.13/library/dataclasses.html