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: Если 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__(). Эти методы сравнивают класс так, как если бы он был кортежем его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа. Еслиordertrue, аeqfalse, то генерируется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оба true, то по умолчаниюdataclass()сгенерирует метод__hash__(). Еслиeqtrue, аfrozenfalse,__hash__()будет установлено вNone, отмечая его как нехэшируемый (поскольку он изменяем). Еслиeqfalse,__hash__()останется без изменений, и будет использован метод__hash__()суперкласса (если суперкласс этоobject, это означает, что будет использоваться хэширование, основанное на id). -
frozen: Если true (по умолчаниюFalse), присвоение полям сгенерирует исключение. Это эмулирует чтение-только замороженные экземпляры. Если__setattr__()или__delattr__()определены в классе, то генерируетсяTypeError. Смотрите обсуждение ниже.
-
fields может необязательно указать значение по умолчанию, используя обычный синтаксис 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: Если истинно (по умолчанию), это поле включается в качестве параметра в генерируемый метод__init__(). -
repr: Если истинно (по умолчанию), это поле включается в строку, возвращаемую сгенерированным методом__repr__(). -
compare: Если истинно (по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (__eq__(),__gt__()и т. д.). -
hash: Это может быть bool илиNone. Если истинно, это поле включается в сгенерированный метод__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 или экземпляр одной. Не возвращает псевдо-поля, которые являютсяClassVarилиInitVar.
-
dataclasses.asdict(instance, *, dict_factory=dict) -
Преобразует dataclass
instanceв словарь (используя функцию-фабрикуdict_factory). Каждая dataclass преобразуется в словарь своих полей, как парыname: value. 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}]}Поднимает
TypeError, еслиinstanceне является экземпляром dataclass.
-
dataclasses.astuple(instance, *, tuple_factory=tuple) -
Преобразует dataclass
instanceв кортеж (используя функцию-фабрикуtuple_factory). Каждая dataclass преобразуется в кортеж значений своих полей. dataclass, словари, списки и кортежи рекурсивно преобразуются.Продолжая предыдущий пример:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
Поднимает
TypeError, еслиinstanceне является экземпляром 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(instance, **changes) -
Создаёт новый объект того же типа, что и
instance, заменяя поля значениями изchanges. Еслиinstanceне является классом данных, генерируется исключение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(class_or_instance) -
Возвращает
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
См. раздел ниже об инициализируемых только при создании переменных, чтобы узнать, как передавать параметры в __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() указывает функцию по умолчанию, она вызывается без аргументов, когда требуется значение по умолчанию для поля. Например, для создания нового списка используйте:
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. Поскольку классы данных просто используют обычное создание классов 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.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/dataclasses.html