Spec-Zone.ru › Python 3.7

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 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__() в вашем dataclass и устанавливать значение 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. Смотрите обсуждение ниже.

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

Spec-Zone.ru

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