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, иfrozenFalse, то__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.
