dataclasses
— Классы данных¶
Исходный код: 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.
Декораторы, классы и функции уровня модуля¶
-
@
dataclasses.
dataclass
(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=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) 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
. См. обсуждение ниже.
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, repr=True, hash=None, init=True, compare=True, metadata=None)¶ Для обычных и простых вариантов использования другие функциональные возможности не требуются. Однако существуют некоторые функции данных класса, требующие дополнительной информации для каждого поля. Для удовлетворения этой потребности в дополнительной информации можно заменить поле значение по умолчанию вызовом предоставленной функции
field()
. Например:@dataclass class C: mylist: List[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]
Как показано выше,
MISSING
значение является сторожевым объектом используемый для обнаружения того, предусмотрены ли параметрыdefault
иdefault_factory
. Этот дозор является используемый, потому чтоNone
является допустимым значение дляdefault
. Никакие код не должны напрямую использоватьMISSING
значение.Параметры для
field()
:default
: если предоставлено, оно будет значением по умолчанию для этого поля. Это необходимо, потому чтоfield()
вызывает себя, заменяя нормальное положение значение по умолчанию.default_factory
: если предоставлено, это должен быть вызываемый нулевой аргумент, который вызовется, когда значение по умолчанию будет необходимо для этого поля. Среди других целей это может быть использовано для определения полей с изменяемыми значениями по умолчанию, как обсуждается ниже. Ошибка при указанииdefault
иdefault_factory
.init
: если True (значение по умолчанию), это поле включается в качестве параметра в генерируемого метод__init__()
.repr
: если True (значение по умолчанию), это поле включается в строку, возвращаемой сгенерированным__repr__()
методом.compare
: если True (значение по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (__eq__()
,__gt__()
и др.).hash
: это может быть буль илиNone
. Если значение равно True, то это поле включается в сгенерированные__hash__()
метод. ЕслиNone
(значение по умолчанию), используется значениеcompare
: это обычно ожидаемое поведение. Поле должно учитываться в хэше, если оно использется для сравнения. Установка этого значение на что-либо другое, кромеNone
, не рекомендуется.Одна из возможных причин для установки
hash=False
, ноcompare=True
была бы, если бы поле дорого вычисляемым хэш- значением необходимым для проверки равенства, и есть другие поля, которые вносят вклад в хэш- значение типа. Даже если поле исключено из hash, оно все равно будет использоваться для сравнения.metadata
: это может быть отображение или None. None рассматривается как пустой dict. Это значение упаковано вMappingProxyType()
, чтобы сделать его доступным только для чтения, и выставляется объектомField
. Он не используется вообще классами данных и обеспечено как сторонний дополнительный механизм. Каждый из нескольких третьих лиц может иметь свой собственный ключ для использования metadata в качестве пространства имен.
Если значение по умолчанию поля определяется вызовом
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
имеют то же значение и значения, что и в объявленииfield()
.
Другие атрибуты могут существовать, но они являются приватными и не должны проверяться или зависеть от них.
-
dataclasses.
fields
(class_or_instance)¶ Возвращает кортеж объектов
Field
, определяющих поля для этого класса данных. Принимает или dataclass или сущность dataclass. ВызываетTypeError
, если не передан класс данных или сущность одного из них. Не возвращает псевдополя, которые являютсяClassVar
илиInitVar
.
-
dataclasses.
asdict
(instance, *, dict_factory=dict)¶ Преобразует dataclass
instance
в dict (с помощью функции фабрикиdict_factory
). Каждый dataclass преобразуется в dict его полей в виде парname: value
. Датаклассы, словари, списки и кортежи рекурсивно. Например:@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}]}
Вызывает
TypeError
, еслиinstance
не является сущностью класса данных.
-
dataclasses.
astuple
(instance, *, tuple_factory=tuple)¶ Преобразует класс данных
instance
в кортеж (с помощью функции фабрикиtuple_factory
). Каждый dataclass преобразуется в кортеж значений своих полей. dataclass, словари, списки и кортежи рекурсивно.Продолжение из предыдущего примера:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
Вызывает
TypeError
, еслиinstance
не является сущностью класса данных.
-
dataclasses.
make_dataclass
(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)¶ Создает новый класс данных с именем
cls_name
, полями, определенными вfields
, базовыми классы, заданными вbases
, и инициализированными с пространством имен, указанным вnamespace
.fields
- итератор, каждый из элементов которого являетсяname
,(name, type)
или(name, type, Field)
. Если указано толькоname
,typing.Any
используется дляtype
. У значенияinit
,repr
,eq
,order
,unsafe_hash
иfrozen
есть то же значение, как они имеют вdataclass()
.Эта функция строгости не требует, потому что любой механизм Python для создания нового класса с
__annotations__
может применить функциюdataclass()
, чтобы преобразовать это класс в dataclass. Эта функция предоставляется для удобства. Например:C = make_dataclass('C', [('x', int), 'y', ('z', int, field(default=5))], namespace={'add_one': lambda self: self.x + 1})
Эквивалентно:
@dataclass class C: x: int y: 'typing.Any' z: int = 5 def add_one(self): return self.x + 1
-
dataclasses.
replace
(instance, **changes)¶ Создает новый объект того же типа
instance
, заменяя поля на значения изchanges
. Еслиinstance
не является классом данных, поднимаетсяTypeError
. Если значения вchanges
не определяют поля, поднимаетсяTypeError
.Вновь возвращенный объект создается путем вызова
__init__()
метода класса данных. Это гарантирует, что__post_init__()
, если он присутствует, также вызывается.Только init переменные без значений по умолчанию, если они существуют, должны быть определены в вызове
replace()
так, чтобы они могли быть переданы к__init__()
и__post_init__()
.Будет ошибкой для
changes
, содержажим любые поля, которые определены какinit=False
. Будет поднято в этом случаеValueError
.Предупреждение о том, как
init=False
поля работают во время вызоваreplace()
. Они не копируются из исходного объекта, а инициализируются в__post_init__()
, если они вообще инициализированы. Ожидается, чтоinit=False
поля будут редко и разумно использоваться. Если они используются, возможно более разумно иметь альтернативные конструкторы класса или возможно пользовательскимreplace()
(или аналогично названный) метод, который обрабатывает копирование сущности.
-
dataclasses.
is_dataclass
(class_or_instance)¶ Возвращает
True
, если его параметром является классом данных или его сущностью, в противном случае возвратитьFalse
.Если вы должны знать, является ли класс сущностью dataclass (и не сам dataclass), то добавьте дальнейшую проверку на
not isinstance(obj, type)
:def is_dataclass_instance(obj): return is_dataclass(obj) and not isinstance(obj, type)
Обработка Post-init¶
Созданный __init__()
код вызовет метод с именем __post_init__()
, если
__post_init__()
определен в классе. Обычно он называется как self.__post_init__()
. Однако,
если определены какие-либо поля InitVar
, они также будут переданы __post_init__()
в порядке, определенном в классе. Если __init__()
метод не
генерируется, то __post_init__()
не вызывается автоматически. Среди других
применений - это инициализация полей значениями, которые зависят от
одного или нескольких других полей. Например:
@dataclass
class C:
a: float
b: float
c: float = field(init=False)
def __post_init__(self):
self.c = self.a + self.b
Способы передачи параметров в __post_init__()
см. в разделе ниже, посвященном
переменным только для инициализации. Также см. предупреждение о том, как
replace()
обрабатывает поля init=False
.
Переменные класса¶
Одно из двух мест, где dataclass()
фактически проверяет тип поля, состоит в
определении того, является ли поле переменной класса, определяемой в соотвествии с
PEP 526. Для этого необходимо проверить, является ли тип поля typing.ClassVar
.
Если поле является ClassVar
, оно исключается из рассмотрения как поле и
игнорируется механизмами класса данных. Такие псевдополя ClassVar
не
возвращены функцией уровня модуля fields()
.
Только Init переменные¶
Другое место, где dataclass()
осматривает аннотацию типа, состоит в том, чтобы
определить, является ли поле init-единственной переменной. Это выполняется путем
просмотра типа поля типа dataclasses.InitVar
. Если поле является InitVar
, оно
считается псевдополем, называемым полем только для инициализации. Поскольку это
не истинное поле, это не возвращается функцией уровня модуля fields()
. Init-
только поля добавлены как параметры к произведенному методу __init__()
и
пройдены к дополнительному __post_init__()
методу. Иначе они не используются в
дата классах.
Например, предположим, что поле будет инициализировано из базы данных, если значение не будет обеспечен, создавая класс:
@dataclass
class C:
i: int
j: int = None
database: InitVar[DatabaseType] = 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):
Функции фабрики по умолчанию¶
Если
field()
задаетdefault_factory
, он вызывается с нулевыми аргументами, когда для поля требуется значение по умолчанию. Например, для создания новой сущности списка используйте:mylist: list = field(default_factory=list)Если поле исключено из
__init__()
(с помощьюinit=False
), а поле также определяетdefault_factory
, то функция фабрика по умолчанию всегда будет вызываться из созданной функции__init__()
. Это происходит, потому что нет никакого другого способа дать полю начальное значение.
Изменяемые значения по умолчанию¶
Python хранит значение переменной элемента по умолчанию в атрибуте класса. Рассмотрим пример, не используя классы данных:
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 class D: x: List = [] def add(self, element): self.x += elementэто произвело бы подобный код:
class D: x = [] def __init__(self, x=x): self.x = x def add(self, element): self.x += element assert D().x is D().xОн имеет ту же проблему, что и исходный пример с использованием класса
C
. Таким образом, два сущности классаD
, которые не определяют значение дляx
, создая сущность класса, разделят ту же копиюx
. Поскольку dataclasses просто используют нормальное созданный Python класс, они также разделяют это поведение. Для классов данных не существует общего способа обнаружения этого состояния. Вместо этого классы данных поднимутTypeError
, если они обнаружат параметр по умолчанию типаlist
,dict
илиset
. Это частичное решение, но оно защищает от многих распространенных ошибок.Использование фабричных функций по умолчанию - это способ создания новых сущностей изменяемых типов в качестве значения по умолчанию для полей:
@dataclass class D: x: list = field(default_factory=list) assert D().x is not D().x
Исключения¶
-
exception
dataclasses.
FrozenInstanceError
¶ Поднимается, когда неявно определенный
__setattr__()
или__delattr__()
вызывается в классе данных, который был определен сfrozen=True
.