Spec-Zone.ru › Python 3.8

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__(). Эти методы сравнивают класс как кортеж из его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа. Если 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: Если True (по умолчанию 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, и предоставляется как механизм расширения для сторонних разработчиков. Несколько сторонних разработчиков могут иметь собственный ключ, чтобы использовать его в качестве пространства имён в метаданных.

Если значение по умолчанию поля задано вызовом 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 не является классом Data, возбуждает TypeError. Если значения в changes не указывают поля, возбуждает TypeError.

Создаваемый объект создаётся путём вызова метода __init__() dataclass. Это гарантирует, что __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)

Обработка после инициализации

Сгенерированный код __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, оно исключается из рассмотрения как поле и игнорируется механизмами dataclass. Такие ClassVar псевдополя не возвращаются функцией уровня модуля fields().

Переменные, используемые только при инициализации

Другое место, где dataclass() проверяет аннотацию типа, — это определение, является ли поле переменной, используемой только при инициализации. Это делается путем проверки, является ли тип поля типа dataclasses.InitVar. Если поле является InitVar, оно считается псевдополем, называемым полем, используемым только при инициализации. Поскольку это не настоящее поле, оно не возвращается функцией уровня модуля fields(). Поля, используемые только при инициализации, добавляются в качестве параметров к сгенерированному методу __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.

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

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

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

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

Когда 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. Окончательный тип 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.8/library/dataclasses.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API