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

Исходный код: Lib/dataclasses.py


Этот модуль предоставляет декоратор и функции для автоматического добавления сгенерированных special method, таких как __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)

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

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

Порядок расположения полей во всех сгенерированных методах соответствует порядку, в котором они отображаются в определении класса.

Декоратор @dataclass добавит в класс различные методы «dunder», описанные ниже. Если какой-либо из добавленных методов уже существует в классе, его поведение зависит от параметра, как описано ниже. Декоратор возвращает тот же класс, для которого он был вызван; новый класс не создается.

Если @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__(): Если значение равно true (по умолчанию), то будет сгенерирован метод .

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

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

  • Если класс уже определяет eq, этот __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__(). Установка атрибута class __hash__ = None имеет особое значение для Python, как описано в документации __hash__().

    По умолчанию __hash__() не будет неявно добавлять метод None, если это не является безопасным. Также не будет добавлен или изменен существующий явно определенный метод @dataclass. Установка атрибута class __hash__() имеет особое значение для Python, как описано в документации @dataclass __hash__() unsafe_hash=True .

    По умолчанию __hash__() не будет неявно добавлять метод __hash__(), если это не является безопасным. Также не будет добавлен или изменен существующий явно определенный метод unsafe_hash=True. Установка атрибута class TypeError имеет особое значение для Python, как описано в документации .

    По умолчанию eq не будет неявно добавлять метод frozen, если это не является безопасным. Также не будет добавлен или изменен существующий явно определенный метод @dataclass. Установка атрибута class __hash__() имеет особое значение для Python, как описано в документации eq frozen __hash__() None eq __hash__() __hash__() object .

  • По умолчанию frozen не будет неявно добавлять метод False, если это не является безопасным. Также не будет добавлен или изменен существующий явно определенный метод __setattr__(). Установка атрибута class __delattr__() имеет особое значение для Python, как описано в документации TypeError.

  • match_args: Если значение равно true (по умолчанию используется значение True), кортеж __match_args__ будет создан из списка параметров для сгенерированного метода __init__() (даже если __init__() не генерируется, см. выше). Если значение false или если __match_args__ уже определено в классе, то __match_args__ генерироваться не будет.

Добавлено в версии 3.10.

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

Добавлено в версии 3.10.

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

Добавлено в версии 3.10.

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

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

Добавлено в версии 3.11.

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

Для обычных и простых случаев использования другие функциональные возможности не требуются. Однако существуют некоторые функции класса данных, для которых требуется дополнительная информация для каждого поля. Чтобы удовлетворить эту потребность в дополнительной информации, вы можете заменить значение поля по умолчанию вызовом предоставленной функции 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: Если значение равно true (по умолчанию), это поле включается в качестве параметра в сгенерированный метод None __hash__() None compare None .

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

  • Одной из возможных причин для установки compare, но __eq__() может __gt__() быть то, что вычисление хэш-значения для поля является дорогостоящим, это поле необходимо для проверки на равенство, и есть другие поля, которые влияют на хэш-значение типа. Даже если поле будет исключено из хэша, оно все равно будет использоваться для сравнения.

  • metadata: Это может быть отображение или None. None рассматривается как пустой dict. Это значение заключено в MappingProxyType(), чтобы оно было доступно только для чтения, и отображается в объекте Field. Он вообще не используется классами данных и предоставляется как сторонний механизм расширения. У нескольких сторонних разработчиков может быть свой собственный ключ для использования в качестве пространства имен в метаданных.

  • kw_only: Это может быть отображение или None. None рассматривается как пустой dict. Это значение заключено в __init__(), чтобы оно было доступно только для чтения, и отображается в объекте . Он вообще не используется классами данных и предоставляется как сторонний механизм расширения. У нескольких сторонних разработчиков может быть свой собственный ключ для использования в качестве пространства имен в метаданных.

Добавлено в версии 3.10.

Если значение поля по умолчанию задано с помощью вызова field(), то атрибут class для этого поля будет заменен на указанное значение default. Если default не указан, то атрибут class будет удален. Цель состоит в том, чтобы после запуска декоратора @dataclass все атрибуты class содержали значения по умолчанию для полей, как если бы было указано само значение по умолчанию. Например, после того, как:

@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().

Other attributes may exist, but they are private and must not be inspected or relied on.

dataclasses.fields(class_or_instance)

Field, TypeError, ClassVar, InitVar, , , , и имеют то же значение, что и в функции .

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

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

Преобразует класс данных asdict() в dict (с помощью заводской функции ). Каждый класс данных преобразуется в dict из своих полей в виде пар . классы данных, диктовки, списки и кортежи преобразуются в. Остальные объекты копируются с помощью .

@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 не является экземпляром класса данных.

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

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

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

@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1
dataclasses.replace(obj, /, **changes)

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с obj затем может применить функцию changes для преобразования obj этого TypeError класса changes в класс TypeError данных. Эта функция предоставляется для удобства. Например:

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с __init__() затем может применить функцию __post_init__ для преобразования этого класса в класс данных. Эта функция предоставляется для удобства. Например:

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с replace() затем может применить функцию __init__() для преобразования __post_init__() этого класса в класс данных. Эта функция предоставляется для удобства. Например:

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с changes затем может применить функцию init=False для преобразования ValueError этого класса в класс данных. Эта функция предоставляется для удобства. Например:

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с init=False затем может применить функцию replace() для преобразования __post_init__() этого init=False класса replace() в класс данных. Эта функция предоставляется для удобства. Например:

dataclasses.is_dataclass(obj)

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

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с not isinstance(obj, type) затем может применить функцию для преобразования этого класса в класс данных. Эта функция предоставляется для удобства. Например:

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

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

dataclasses.KW_ONLY

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с KW_ONLY затем может применить функцию KW_ONLY для преобразования _ этого KW_ONLY класса __init__() в класс данных. Эта функция предоставляется для удобства. Например:

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

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

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

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

Добавлено в версии 3.10.

exception dataclasses.FrozenInstanceError

Эта функция не является строго обязательной, поскольку любой механизм Python для создания нового класса с __setattr__() затем может применить функцию __delattr__() для преобразования frozen=True этого AttributeError класса в класс данных. Эта функция предоставляется для удобства. Например:

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

Сгенерированный __init__() код вызовет метод с именем __post_init__(), если в классе определен __post_init__(). Обычно он вызывается как self.__post_init__(). Однако, если определены какие-либо поля InitVar, они также будут переданы в __post_init__() в том порядке, в котором они были определены в классе. Если метод __init__() не сгенерирован, то метод __post_init__() не будет вызван автоматически.

Когда он определен в классе, он будет вызываться сгенерированным __init__(), обычно как self.__post_init__(). Однако, если определены какие-либо поля InitVar, они также будут переданы в __post_init__() в том порядке, в котором они были определены в классе. Если метод __init__() не сгенерирован, то метод __post_init__() не будет вызван автоматически.

@класс C класса dataclass:

a: float b: float c: float = поле(init=False)

def __post_init__(сам):

self.c = self.a + self.b

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

class Rectangle:
    def __init__(self, height, width):
      self.height = height
      self.width = width

@dataclass
class Square(Rectangle):
    side: float

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

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

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

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

@dataclass <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__() невозможно использовать простое присваивание для инициализации полей и необходимо использовать __setattr__(), что приводит к незначительному снижению производительности.

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

Когда декоратор @dataclass создает класс данных, он просматривает все базовые классы класса в обратном порядке (то есть, начиная с 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() указывает значение default_factory, оно вызывается с нулевыми аргументами, когда требуется значение по умолчанию для поля. Например, для создания нового экземпляра списка используйте:

mylist: list = field(default_factory=list)

Если поле исключено из __init__() (с использованием init=False) и в поле также указано default_factory, то заводская функция по умолчанию всегда будет вызываться из сгенерированной функции __init__(). Это происходит потому, что нет другого способа присвоить полю начальное значение.

Изменяемые значения по умолчанию

Python хранит значения переменных-членов по умолчанию в атрибутах класса. Рассмотрим этот пример, не используя классы данных:

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

Обратите внимание, что два экземпляра класса C совместно используют одну и ту же переменную класса x, как и ожидалось.

Используя классы данных, если этот код был корректным:

@dataclass
class D:
    x: list = []      # This code raises ValueError
    def add(self, element):
        self.x.append(element)

это сгенерировало бы код, аналогичный:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x.append(element)

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

Здесь возникает та же проблема, что и в исходном примере с использованием класса C. То есть два экземпляра класса D, которые не задают значение для x при создании экземпляра класса, будут совместно использовать одну и ту же копию x. Поскольку классы данных просто используют обычное создание классов Python, они также ведут себя подобным образом. Для классов данных не существует общего способа обнаружения этого состояния. Вместо этого @dataclass декоратор создаст ValueError, если он обнаружит параметр по умолчанию, который нельзя хэшировать. Предполагается, что если значение не может быть хэшировано, оно может изменяться. Это частичное решение, но оно действительно защищает от многих распространенных ошибок.

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

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

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

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

Поля, типизированные с помощью дескриптора

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

  • Значение поля, переданное в метод __init__() класса данных, передается в метод __set__() дескриптора, а не перезаписывает объект дескриптора.

  • Аналогично, при получении или установке поля вызывается метод descriptors __get__() или __set__(), а не возвращается или перезаписывается объект-дескриптор.

  • Чтобы определить, содержит ли поле значение по умолчанию, @dataclass вызовет метод дескриптора __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

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

Вернуться на верх