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) -
Эта функция является декоратором, который используется для добавления сгенерированных специальных методов к классам, как описано ниже.
Декоратор
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) 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. Смотрите обсуждение ниже.
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, repr=True, hash=None, init=True, compare=True, metadata=None) -
Для распространённых и простых случаев использования других функций не требуется. Однако существуют некоторые особенности dataclass, которые требуют дополнительной информации для каждого поля. Для удовлетворения этой потребности в дополнительной информации вы можете заменить значение поля по умолчанию вызовом предоставленной функции
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: Может быть bool илиNone. Если значение True, это поле включается в сгенерированный метод__hash__(). ЕслиNone(значение по умолчанию), используется значениеcompare: это обычно ожидаемое поведение. Поле должно учитываться в хэше, если оно используется для сравнений. Не рекомендуется устанавливать это значение на что-либо отличное отNone.Одна из возможных причин установить
hash=Falseно неcompare=Trueзаключается в том, что для поля дорого вычислить значение хэша, это поле необходимо для проверки равенства, а есть другие поля, которые вносят вклад в хэш типа. Даже если поле исключается из хэша, оно всё равно используется для сравнения. -
metadata: Может быть словарем или None. None обрабатывается как пустой словарь. Это значение обернуто вMappingProxyType()для обеспечения его неизменяемости и показано в объектеField. Оно вообще не используется Data Classes и предоставляется как механизм расширения для сторонних библиотек. Различные сторонние библиотеки могут иметь свои собственные ключи для использования в качестве имени пространства имён в метаданных.
Если значение по умолчанию поля задаётся вызовом
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, либо экземпляр dataclass. ВызываетTypeError, если передан не dataclass или не экземпляр dataclass. Не возвращает псевдополя, которые являютсяClassVarилиInitVar.
-
dataclasses.asdict(obj, *, dict_factory=dict) -
Преобразует dataclass
objв словарь (с использованием фабричной функцииdict_factory). Каждая dataclass преобразуется в словарь её полей, как парыname: value. dataclass, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощьюcopy.deepcopy().Пример использования
asdict()с вложенными dataclass:@dataclass class Point: x: int y: int @dataclass class C: mylist: list[Point] p = Point(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Point(0, 0), Point(10, 4)]) assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}Для создания поверхностной копии можно использовать следующий обходной путь:
dict((field.name, getattr(obj, field.name)) for field in fields(obj))
asdict()вызываетTypeError, еслиobjне является экземпляром dataclass.
-
dataclasses.astuple(obj, *, tuple_factory=tuple) -
Преобразует dataclass
objв кортеж (с использованием фабричной функцииtuple_factory). Каждая dataclass преобразуется в кортеж значений её полей. dataclass, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощью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не является экземпляром dataclass.
-
dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False) -
Создаёт новую dataclass с именем
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(obj, /, **changes) -
Создаёт новый объект того же типа, что и
obj, заменяя поля значениями изchanges. Еслиobjне является классом данных, возникает исключениеTypeError. Если значения вchangesне соответствуют полям, возникает исключениеTypeError.Новый возвращаемый объект создаётся путём вызова метода
__init__()класса данных. Это гарантирует, что__post_init__(), если он присутствует, также будет вызван.Переменные, используемые только для инициализации, без значений по умолчанию, если таковые имеются, должны быть указаны при вызове
replace(), чтобы они могли быть переданы в__init__()и__post_init__().Ошибка возникает, если
changesсодержит поля, определённые как имеющиеinit=False. В этом случае будет поднято исключениеValueError.Обратите внимание на то, как работают поля
init=Falseпри вызовеreplace(). Они не копируются из исходного объекта, а вместо этого инициализируются в__post_init__(), если они вообще инициализируются. Ожидается, что поляinit=Falseбудут использоваться редко и осмысленно. Если они используются, разумно иметь альтернативные конструкторы класса или, возможно, пользовательский методreplace()(или с аналогичным названием), обрабатывающий копирование экземпляра.
-
dataclasses.is_dataclass(obj) -
Возвращает
True, если его параметр является классом данных или экземпляром одного из них, в противном случае возвращаетFalse.Если вам нужно узнать, является ли класс экземпляром класса данных (а не самим классом данных), добавьте дополнительную проверку на
not isinstance(obj, type):def is_dataclass_instance(obj): return is_dataclass(obj) and not isinstance(obj, type)
Обработка после инициализации
Сгенерированный код __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
Метод __init__(), сгенерированный dataclass(), не вызывает методы __init__() базового класса. Если базовый класс имеет метод __init__(), который необходимо вызвать, обычно это делается в методе __post_init__():
@dataclass
class Rectangle:
height: float
width: float
@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
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 хранит значения по умолчанию для переменных-членов в атрибутах класса. Рассмотрим этот пример, не используя 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 = []
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, они также демонстрируют такое поведение. Нет общего способа для Data Classes обнаружить это условие. Вместо этого dataclasses будут поднимать 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__()вызывается для dataclass, который был определён сfrozen=True. Это подклассAttributeError.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/dataclasses.html