Spec-Zone.ru › Python 3.11

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, match_args=True, kw_only=False, slots=False, weakref_slot=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,
           match_args=True, kw_only=False, slots=False, weakref_slot=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. См. обсуждение ниже.
  • match_args: Если значение true (по умолчанию True), кортеж __match_args__ будет создан из списка параметров сгенерированного метода __init__() (даже если __init__() не сгенерирован, см. выше). Если значение false или если __match_args__ уже определён в классе, то __match_args__ не будет сгенерирован.

Новое в версии 3.10.

  • kw_only: Если истинно (значение по умолчанию — False), все поля будут помечены как только для ключевых слов. Если поле помечено как только для ключевых слов, то единственное последствие состоит в том, что параметр __init__(), сгенерированный из поля, только для ключевых слов, должен быть указан с ключевым словом при вызове __init__(). Это никак не повлияет на другие аспекты dataclasses. См. запись в глоссарии параметра для получения подробностей. Также см. раздел KW_ONLY.

Новая версия 3.10.

  • slots: Если истинно (значение по умолчанию — False), атрибут __slots__ будет сгенерирован, и вместо исходного класса будет возвращен новый. Если __slots__ уже определен в классе, то будет поднята ошибка TypeError.

Новая версия 3.10.

Изменено в версии 3.11: Если имя поля уже включено в __slots__ базового класса, оно не будет включено в сгенерированное __slots__ для предотвращения их перезаписи. Поэтому не используйте __slots__ для получения имен полей dataclass. Используйте fields() вместо этого. Для определения унаследованных слотов базовый класс __slots__ может быть любым итерируемым объектом, но не итератором.

  • weakref_slot: Если истинно (значение по умолчанию — False), добавляется слот с именем «__weakref__», необходимый для возможности создания слабой ссылки на экземпляр. Указание weakref_slot=True без одновременного указания slots=True является ошибкой.

Новая версия 3.11.

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, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING)

Для обычных и простых случаев использования другой функциональности не требуется. Однако некоторые особенности dataclass требуют дополнительной информации для каждого поля. Чтобы удовлетворить эту потребность в дополнительной информации, вы можете заменить значение поля по умолчанию вызовом предоставленной функции field(). Например:

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

Как показано выше, значение MISSING — это специальный объект, используемый для определения того, были ли некоторые параметры предоставлены пользователем. Этот специальный объект используется потому, что None — это допустимое значение для некоторых параметров с другим значением. Ни один код не должен напрямую использовать значение MISSING.

Параметры field():

  • default: Если указано, это будет значение по умолчанию для данного поля. Это необходимо, потому что сам вызов field() заменяет обычное расположение значения по умолчанию.
  • default_factory: Если указано, это должна быть функция без аргументов, которая будет вызываться при необходимости значения по умолчанию для этого поля. Среди прочего, это может использоваться для задания полей с изменяемыми значениями по умолчанию, как обсуждалось ниже. Нельзя одновременно указать default и default_factory.
  • init: Если истинно (по умолчанию), это поле включается в качестве параметра в сгенерированный метод __init__().
  • repr: Если истинно (по умолчанию), это поле включается в строку, возвращаемую сгенерированным методом __repr__().
  • hash: Может быть булевым значением или None. Если истинно, это поле включается в сгенерированный метод __hash__(). Если None (по умолчанию), используется значение compare; обычно это ожидаемое поведение. Поле должно учитываться в хеше, если оно используется для сравнения. Не рекомендуется устанавливать это значение на что-то отличное от None.

    Одна возможная причина установить hash=False, но compare=True, может быть в том, что поле является дорогостоящим для вычисления значения хеша, это поле необходимо для проверки на равенство, и есть другие поля, которые вносят вклад в хеш значения типа. Даже если поле исключается из хеша, оно по-прежнему будет использоваться для сравнения.

  • compare: Если истинно (по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (__eq__(), __gt__() и др.).
  • metadata: Может быть словарем или None. None рассматривается как пустой словарь. Это значение оборачивается в MappingProxyType(), чтобы сделать его неизменяемым, и оно доступно в объекте Field. Оно вообще не используется dataclass и предоставляется как механизм расширения третьих сторон. Несколько сторонних организаций могут использовать каждый свой ключ в качестве пространства имен в метаданных.
  • kw_only: Если истинно, это поле будет помечено как только для ключевых слов. Это используется при вычислении параметров сгенерированного метода __init__().

Новая версия 3.10.

Если значение по умолчанию для поля задано вызовом 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, и kw_only имеют идентичное значение и значения, как и в функции field().

Другие атрибуты могут существовать, но они являются приватными и их нельзя инспектировать или на них полагаться.

dataclasses.fields(class_or_instance)

Возвращает кортеж объектов Field, которые определяют поля для этого dataclass. Принимает либо dataclass, либо экземпляр dataclass. Поднимает исключение TypeError, если не передано dataclass или экземпляр dataclass. Не возвращает псевдополя, которые являются ClassVar или InitVar.

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

Преобразует dataclass obj в словарь (используя функцию-фабрику dict_factory). Каждый dataclass преобразуется в словарь своих полей, как пары name: value . dataclasses, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощью copy.deepcopy().

Пример использования asdict() для вложенных 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}]}

Для создания поверхностной копии можно использовать следующее решение:

dict((field.name, getattr(obj, field.name)) for field in fields(obj))

asdict() поднимает TypeError, если obj не является экземпляром dataclass.

dataclasses.astuple(obj, *, tuple_factory=tuple)

Преобразует датакласс obj в кортеж (используя функцию-фабрику tuple_factory). Каждый датакласс преобразуется в кортеж значений его полей. dataclasses, словари, списки и кортежи рекурсивно преобразуются. Другие объекты копируются с помощью copy.deepcopy().

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

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

Для создания поверхностной копии можно использовать следующий обходной путь:

tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))

astuple() генерирует TypeError, если obj не является экземпляром датакласса.

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

Создаёт новый датакласс с именем cls_name, полями, определёнными в fields, базовыми классами, указанными в bases, и инициализированными в пространстве имён, заданном в namespace. fields — это итерируемый объект, элементы которого являются либо name, либо (name, type), либо (name, type, Field). Если указан только name, то typing.Any используется для type. Значения init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, и weakref_slot имеют такое же значение, как и в 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(obj, /, **changes)

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

Возвращает True, если его параметр является датаклассом или экземпляром датакласса, в противном случае возвращает False.

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

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

Значение-сентинэл, обозначающее отсутствие значения по умолчанию или factory по умолчанию.

dataclasses.KW_ONLY

Значение-сентинэл, используемое в качестве аннотации типа. Любые поля после псевдополя с типом KW_ONLY отмечаются как поля, доступные только по ключевым словам. Обратите внимание, что псевдополе типа KW_ONLY в противном случае полностью игнорируется. Это включает и имя такого поля. По соглашению, имя _ используется для поля KW_ONLY. Поля, доступные только по ключевым словам, обозначают параметры __init__(), которые должны быть указаны в качестве ключевых слов при создании экземпляра класса.

В этом примере поля y и z будут помечены как поля, доступные только по ключевым словам:

@dataclass
class Point:
    x: float
    _: KW_ONLY
    y: float
    z: float

p = Point(0, y=1.5, z=2.0)

В одном датаклассе запрещено указывать более одного поля с типом KW_ONLY.

Новое в версии 3.10.

exception dataclasses.FrozenInstanceError

Генерируется, когда неявно определённый __setattr__() или __delattr__() вызывается на датаклассе, который был определён с frozen=True. Это подкласс AttributeError.

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

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

Метод __init__(), сгенерированный dataclass(), не вызывает базовые методы __init__(). Если базовый класс имеет метод __init__(), который нужно вызвать, обычно это делается в методе __post_init__():

@dataclass
class Rectangle:
    height: float
    width: float

@dataclass
class Square(Rectangle):
    side: float

    def __post_init__(self):
        super().__init__(self.side, self.side)

Однако, в общем случае, сгенерированные методы __init__() датакласса не обязательно вызывать, так как производный датакласс позаботится об инициализации всех полей любого базового класса, являющегося самим датаклассом.

См. раздел ниже о переменных только для инициализации, чтобы узнать, как передавать параметры в __post_init__(). Также см. предупреждение о том, как replace() обрабатывает поля init=False.

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

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

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

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

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

@dataclass
class C:
    i: int
    j: int | None = None
    database: InitVar[DatabaseType | None] = 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):

Переупорядочение параметров только по ключевым словам в __init__()

После вычисления параметров, необходимых для __init__(), все параметры только по ключевым словам перемещаются после всех обычных (не только по ключевым словам) параметров. Это требование для реализации параметров только по ключевым словам в Python: они должны следовать за параметрами не только по ключевым словам.

В этом примере Base.y, Base.w, и D.t — поля только по ключевым словам, а Base.x и D.z — обычные поля:

@dataclass
class Base:
    x: Any = 15.0
    _: KW_ONLY
    y: int = 0
    w: int = 1

@dataclass
class D(Base):
    z: int = 10
    t: int = field(kw_only=True, default=0)

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

def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):

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

Относительный порядок параметров только по ключевым словам сохраняется в переупорядоченном списке параметров __init__().

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

Если 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 = []      # This code raises ValueError
    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, чтобы обнаружить это условие. Вместо этого декоратор dataclass() сгенерирует исключение ValueError, если обнаружит параметр по умолчанию, который нельзя хешировать. Предполагается, что если значение нельзя хешировать, оно изменяемое. Это частичное решение, но оно защищает от многих распространённых ошибок.

Использование функций-фабрик по умолчанию — это способ создания новых экземпляров изменяемых типов в качестве значений по умолчанию для полей:

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

Изменено в версии 3.11: Вместо поиска и запрета объектов типа list, dict, или set, в качестве значений по умолчанию теперь запрещены нехешируемые объекты. Нехешируемость используется для приблизительного определения изменяемости.

Поля с типом дескриптора

Поля, которым присвоено значение объект дескриптора в качестве значения по умолчанию, имеют следующие особые свойства:

  • Значение поля, переданное в метод __init__ класса dataclass, передаётся в метод __set__ дескриптора, а не заменяет объект дескриптора.
  • Аналогично, при получении или установке поля вызывается метод __get__ или __set__ дескриптора, а не возвращается или заменяется объект дескриптора.
  • Для определения, содержит ли поле значение по умолчанию, dataclasses вызовет метод __get__ дескриптора, используя форму доступа к классу (т.е. descriptor.__get__(obj=None, type=cls). Если дескриптор возвращает значение в этом случае, оно используется в качестве значения по умолчанию поля. С другой стороны, если дескриптор генерирует AttributeError в этой ситуации, поле не получит значение по умолчанию).
class IntConversionDescriptor:
    def __init__(self, *, default):
        self._default = default

    def __set_name__(self, owner, name):
        self._name = "_" + name

    def __get__(self, obj, type):
        if obj is None:
            return self._default

        return getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        setattr(obj, self._name, int(value))

@dataclass
class InventoryItem:
    quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)

i = InventoryItem()
print(i.quantity_on_hand)   # 100
i.quantity_on_hand = 2.5    # calls __set__ with 2.5
print(i.quantity_on_hand)   # 2

Обратите внимание, что если поле аннотировано типом дескриптора, но не получает объект дескриптора в качестве значения по умолчанию, оно ведёт себя как обычное поле.

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/dataclasses.html

Spec-Zone.ru

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