Документация Python неофициальный перевод
Содержание страницы

dataclasses – Классы данныхdataclasses – Data Classes

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


Этот модуль предоставляет декоратор и функции для автоматического добавления в пользовательские классы специальных методов, таких как __init__() и __repr__(). Изначально он был описан в PEP 557.

Переменные-члены, используемые в этих сгенерированных методах, определяются с помощью аннотаций типов PEP 526. Например, следующий код:

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Класс для отслеживания предмета в инвентаре."""
    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.

Содержимое модуляModule contents

@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__(), этот параметр игнорируется.

    Изменено в версии 3.13: Сгенерированный метод __eq__ теперь сравнивает каждое поле по отдельности (например, self.a == other.a and self.b == other.b), а не сравнивает кортежи полей, как в предыдущих версиях.

    Это изменение ускоряет сравнение, но может изменить результаты в случаях, когда атрибуты сравниваются по идентичности, а не по значению (например, float('nan')).

    В Python 3.12 и более ранних версиях сравнение выполнялось путём создания кортежей полей и их сравнения (например, (self.a, self.b) == (other.a, other.b)).

  • order: Если true (по умолчанию False), будут сгенерированы методы __lt__(), __le__(), __gt__() и __ge__(). Они сравнивают класс так, как если бы он был кортежем своих полей, по порядку. Оба экземпляра в сравнении должны быть одного и того же типа. Если order установлен в true, а eq – в false, вызывается ValueError.

    Если класс уже определяет любой из __lt__(), __le__(), __gt__() или __ge__(), то вызывается TypeError.

  • unsafe_hash: If true, force dataclasses to create a __hash__() method, even though it may not be safe to do so. Otherwise, generate a __hash__() method according to how eq and frozen are set. The default value is False.

    __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__() в классе данных и устанавливать unsafe_hash=True; это приведёт к TypeError.

    Если и eq, и frozen истинны, по умолчанию @dataclass автоматически сгенерирует метод __hash__(). Если eq истинно, а frozen ложно, __hash__() будет установлен в None, что делает его нехэшируемым (что и так верно, поскольку он изменяем). Если eq ложно, __hash__() остаётся без изменений, то есть будет использоваться метод __hash__() родительского класса (если родительский класс – object, это означает, что хэширование будет основано на идентификаторах).

  • frozen: Если истинно (по умолчанию – False), присваивание полям будет вызывать исключение. Это имитирует неизменяемые экземпляры. См. обсуждение ниже.

    Если в классе определён __setattr__() или __delattr__(), а frozen истинно, то возникает TypeError.

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

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

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

    Только-ключевые поля не включаются в __match_args__.

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

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

Предупреждение

Передача параметров в базовый класс __init_subclass__() при использовании slots=True приведёт к TypeError. Либо используйте __init_subclass__ без параметров, либо укажите значения по умолчанию в качестве обходного пути. Подробнее см. gh-91126.

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

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

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

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

field могут опционально задавать значение по умолчанию, используя обычный синтаксис Python:

@dataclass
class C:
    a: int       # 'a' не имеет значения по умолчанию
    b: int = 0   # задайте значение по умолчанию для '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, doc=None)

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

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

  • compare: Если истинно (по умолчанию), это поле включается в сгенерированные методы равенства и сравнения (__eq__(), __gt__() и т. д.).

  • metadata: Может быть отображением (mapping) или None. None трактуется как пустой словарь. Это значение оборачивается в MappingProxyType() для обеспечения read-only и предоставляется через объект Field. Data Classes его вообще не используют, это механизм расширения для сторонних библиотек. Разные сторонние разработчики могут использовать свои ключи как пространства имён в метаданных.

  • kw_only: Если истинно, это поле помечается как только-ключевое. Используется при вычислении параметров сгенерированного метода __init__().

    Только-ключевые поля также не включаются в __match_args__.

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

  • doc: необязательная строка документации для этого поля.

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

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

Могут существовать и другие атрибуты, но они приватные, и на них не следует полагаться или проверять их.

class dataclasses.InitVar

Аннотации типа InitVar[T] описывают переменные, которые являются только для инициализации. Поля, аннотированные InitVar, считаются псевдополями и, соответственно, не возвращаются функцией fields() и не используются никак, кроме добавления в качестве параметров в __init__() и необязательного __post_init__().

dataclasses.fields(class_or_instance)

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

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

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

Пример использования asdict() на вложенных датаклассах:

@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}]}

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

{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, module=None, decorator=dataclass)

Создаёт новый датакласс с именем cls_name, полями, определёнными в fields, базовыми классами из bases, и инициализированным пространством имён из namespace. fields – это итерируемый объект, элементы которого могут быть name, (name, type) или (name, type, Field). Если указано только name, то для type используется typing.Any. Значения init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots и weakref_slot имеют тот же смысл, что и в @dataclass.

Если определён module, то атрибут __module__ датакласса устанавливается в это значение. По умолчанию он устанавливается в имя модуля вызывающего кода.

Параметр decorator – это вызываемый объект, который будет использоваться для создания датакласса. Он должен принимать объект класса в качестве первого аргумента и те же именованные аргументы, что @dataclass. По умолчанию используется функция @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

Добавлено в версии 3.14: Добавлен параметр decorator.

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() (или с похожим именем), который обрабатывает копирование экземпляра.

Экземпляры датаклассов также поддерживаются обобщённой функцией copy.replace().

dataclasses.is_dataclass(obj)

Возвращает True, если его параметр является dataclass (включая подклассы dataclass, но не включая обобщённые псевдонимы) или экземпляром такого класса, в противном случае возвращает False.

Если нужно узнать, является ли класс экземпляром dataclass (а не самим dataclass), добавьте дополнительную проверку на 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 помечаются как keyword-only поля. Обратите внимание, что псевдополе типа KW_ONLY в остальном полностью игнорируется, включая имя такого поля. По соглашению имя _ используется для поля KW_ONLY. Keyword-only поля означают параметры __init__(), которые должны быть указаны как ключевые слова при создании класса.

В этом примере поля y и z будут помечены как keyword-only поля:

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

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

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

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

exception dataclasses.FrozenInstanceError

Возбуждается, когда неявно определённые __setattr__() или __delattr__() вызываются для dataclass, определённого с frozen=True. Является подклассом AttributeError.

Обработка после инициализацииPost-init processing

dataclasses.__post_init__()

Если метод определён в классе, он будет вызван сгенерированным __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__():

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.

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

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

Переменные только для инициализацииInit-only variables

Ещё одно место, где @dataclass проверяет аннотацию типа, – определение, является ли поле переменной, используемой только для инициализации. Для этого проверяется, имеет ли поле тип 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.

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

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

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

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

Когда 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. Итоговый тип xint, как указано в классе C.

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

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

Переупорядочивание keyword-only параметров в __init__()Re-ordering of keyword-only parameters in __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__().

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

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

mylist: list = field(default_factory=list)

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

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

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 = []      # Этот код вызывает 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. Поскольку dataclasses просто используют обычное создание классов Python, они также разделяют это поведение. Не существует общего способа для Data Classes обнаружить эту ситуацию. Вместо этого декоратор @dataclass вызовет исключение ValueError, если обнаружит нехешируемый параметр по умолчанию. Предполагается, что если значение нехешируемо, то оно изменяемо. Это частичное решение, но оно защищает от многих распространённых ошибок.

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

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

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

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

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

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

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

  • Аналогично, при чтении или записи поля вызывается метод __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    # вызывает __set__ с 2.5
print(i.quantity_on_hand)   # 2

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