Документация Python неофициальный перевод
Содержание страницы

dataclasses – Классы данныхdataclasses – Data Classes

Исходный код: 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.

Декораторы, классы и функции уровня модуляModule-level decorators, classes, and functions

@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)

Для простых и распространённых случаев никакой дополнительной функциональности не требуется. Однако у 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, определяющих поля этого датакласса. Принимает либо датакласс, либо экземпляр датакласса. Вызывает TypeError, если передан не датакласс и не его экземпляр. Не возвращает псевдополя, которые являются ClassVar или InitVar.

dataclasses.asdict(instance, *, dict_factory=dict)

Преобразует dataclass instance в словарь (используя фабричную функцию dict_factory). Каждый dataclass преобразуется в словарь своих полей в виде пар name: value. dataclasses, словари, списки и кортежи обходятся рекурсивно. Например:

@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 преобразуется в кортеж значений своих полей. dataclasses, словари, списки и кортежи обходятся рекурсивно.

Продолжая предыдущий пример:

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, то для type используется typing.Any. Значения init, repr, eq, order, unsafe_hash и frozen имеют тот же смысл, что и в dataclass().

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с помощью __annotations__ может затем применить функцию 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 не является Data Class, вызывает 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, если его параметр является dataclass или экземпляром dataclass, иначе возвращает False.

Если нужно узнать, является ли класс экземпляром dataclass (а не самим dataclass), добавьте дополнительную проверку на not isinstance(obj, type):

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)

Обработка после инициализацииPost-init processing

Сгенерированный код __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.

Переменные классаClass variables

Одно из двух мест, где dataclass() действительно проверяет тип поля – это определение, является ли поле переменной класса, как определено в PEP 526. Это делается путём проверки, является ли тип поля typing.ClassVar. Если поле является ClassVar, оно исключается из рассмотрения как поле и игнорируется механизмами dataclass. Такие ClassVar псевдополя не возвращаются функцией модульного уровня fields().

Переменные только для инициализацииInit-only variables

Другое место, где dataclass() проверяет аннотацию типа – это определение, является ли поле переменной только для инициализации (init-only). Это делается путём проверки, является ли тип поля типом dataclasses.InitVar. Если поле является InitVar, оно считается псевдополем, называемым init-only полем. Поскольку это не настоящее поле, оно не возвращается функцией модульного уровня fields(). Init-only поля добавляются как параметры в сгенерированный метод __init__() и передаются необязательному методу __post_init__(). В остальном они не используются dataclasses.

Например, предположим, что поле будет инициализироваться из базы данных, если значение не указано при создании класса:

@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.

Замороженные экземплярыFrozen instances

Создать по-настоящему неизменяемые объекты Python невозможно. Однако, передав frozen=True декоратору dataclass(), можно эмулировать неизменяемость. В этом случае dataclasses добавит в класс методы __setattr__() и __delattr__(). При вызове эти методы вызовут исключение FrozenInstanceError.

Использование frozen=True сопряжено с небольшим снижением производительности: __init__() не может использовать простое присваивание для инициализации полей и должен использовать object.__setattr__().

НаследованиеInheritance

Когда dataclass создаётся декоратором dataclass(), он просматривает все базовые классы в порядке, обратном MRO (начиная с object), и для каждого найденного dataclass добавляет поля из этого базового класса в упорядоченное отображение полей. После добавления всех полей базовых классов он добавляет свои собственные поля в это упорядоченное отображение. Все сгенерированные методы будут использовать это объединённое вычисленное упорядоченное отображение полей. Поскольку поля располагаются в порядке вставки, производные классы переопределяют базовые. Пример:

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

Итоговый список полей в порядке: x, y, z. Итоговый тип xint, как указано в классе C.

Сгенерированный метод __init__() для C будет выглядеть так:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

Функции фабрики по умолчаниюDefault factory functions

Если field() указывает default_factory, то он вызывается без аргументов, когда требуется значение по умолчанию для поля. Например, чтобы создать новый экземпляр списка, используйте:

mylist: list = field(default_factory=list)

Если поле исключено из __init__() (с помощью init=False) и поле также указывает default_factory, то функция фабрики значений по умолчанию всегда будет вызываться из сгенерированной функции __init__(). Это происходит потому, что нет другого способа задать полю начальное значение.

Изменяемые значения по умолчаниюMutable default values

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

ИсключенияExceptions

exception dataclasses.FrozenInstanceError

Возбуждается, когда неявно определённые __setattr__() или __delattr__() вызываются для dataclass, определённого с frozen=True. Является подклассом AttributeError.