Spec-Zone.ru › Python 3.10

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)

Эта функция является декоратором, который используется для добавления сгенерированных специальных методов к классам, как описано ниже.

Декоратор 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)
class C:
   ...

Параметры для dataclass():

  • init: Если истинно (по умолчанию), будет сгенерирован метод __init__().

    Если класс уже определяет __init__(), этот параметр игнорируется.

  • repr: Если истинно (по умолчанию), будет сгенерирован метод __repr__(). Сгенерированная строка repr будет содержать имя класса и имя и repr каждого поля в порядке их определения в классе. Поля, помеченные как исключенные из repr, не включаются. Например: InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).

    Если класс уже определяет __repr__(), этот параметр игнорируется.

  • eq: Если истинно (по умолчанию), будет сгенерирован метод __eq__(). Этот метод сравнивает класс, как если бы он был кортежем его полей в порядке. Оба экземпляра в сравнении должны быть одного и того же типа.

    Если класс уже определяет __eq__(), этот параметр игнорируется.

  • order: Если истинно (по умолчанию 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: Если истинно (по умолчанию False), присваивание полям будет генерировать исключение. Это имитирует неизменяемые экземпляры только для чтения. Если __setattr__() или __delattr__() определены в классе, то возникает TypeError. См. обсуждение ниже.
  • match_args: Если истинно (по умолчанию True), кортеж __match_args__ будет создан из списка параметров сгенерированного метода __init__() (даже если __init__() не сгенерирован, см. выше). Если ложно, или если __match_args__ уже определен в классе, то __match_args__ не будет сгенерирован.

Введено в версии 3.10.

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

Введено в версии 3.10.

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

Введено в версии 3.10.

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: Если True (значение по умолчанию), это поле включено в качестве параметра для сгенерированного метода __init__().
  • repr: Если True (значение по умолчанию), это поле включено в строку, возвращаемую сгенерированным методом __repr__().
  • hash: Это может быть bool или None. Если True, это поле включено в сгенерированный метод __hash__() . Если None (значение по умолчанию), используется значение compare: это обычно ожидаемое поведение. Поле должно учитываться в хэше, если оно используется для сравнения. Не рекомендуется устанавливать это значение на что-либо отличное от None.

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

  • compare: Если True (значение по умолчанию), это поле включено в сгенерированные методы сравнения и равенства (__eq__(), __gt__(), и т. д.).
  • metadata: Это может быть словарь или None. None обрабатывается как пустой словарь. Это значение обернуто в MappingProxyType() для повышения его неизменяемости и показано в объекте Field. Он вообще не используется классами данных и предоставляется в качестве механизма расширения сторонними разработчиками. Несколько сторонних разработчиков могут иметь свои собственные ключи для использования в качестве пространства имён в метаданных.
  • kw_only: Если True, это поле будет помечено как только для ключевых слов. Это используется при вычислении параметров сгенерированного метода __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() на вложенных 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}]}

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

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

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

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

Преобразует dataclass obj в кортеж (используя фабричную функцию tuple_factory). Каждый dataclass преобразуется в кортеж его значений полей. 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 не является экземпляром dataclass.

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)

Создаёт новый dataclass с именем 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 имеют то же значение, что и в 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(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

Значение-сентинель, обозначающее отсутствие значения по умолчанию или default_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.

New in version 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__() . Они не используются иначе классами данных.

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

@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(), можно смоделировать неизменяемость. В этом случае классы данных добавят методы __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):

Переупорядочение параметров только для ключевых слов в __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() указана функция создания объекта по умолчанию, она вызывается без аргументов, когда требуется значение по умолчанию для поля. Например, для создания нового списка используйте:

mylist: list = field(default_factory=list)

Если поле исключено из __init__() (используя init=False) и также указана функция создания объекта по умолчанию, то эта функция всегда будет вызываться из сгенерированной функции __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 обнаружить это условие. Вместо этого декоратор dataclass() будет поднимать исключение TypeError, если обнаружит параметр по умолчанию типа list, dict, или set. Это частичное решение, но оно защищает от многих распространённых ошибок.

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

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

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

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

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

  • Значение для поля, передаваемое методу __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.10/library/dataclasses.html

Spec-Zone.ru

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