dataclasses — Классы данных


Модуль предоставляет декораторы и функции для того, чтобы автоматически добавлять генерируемые специальные методы __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() добавит к классу различные «тупые» методы, рассмотренные далее. Если какой-либо из добавляемых методов уже существует в классе, то дальнейшее поведение зависит от параметра, как описано ниже. Декоратор возвращает тот же самый класс, который вызывается; новый класс не создается.

Если 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.