Эффективное использование dataclass в объектно-ориентированном Python
Классы данных: упрощение создания объектов
Как быстро создать класс с полями, методами __init__, __repr__ и сравнением объектов?
Модуль dataclasses предоставляет декоратор @dataclass, который автоматически генерирует методы __init__, __repr__, __eq__ и другие. Это сокращает количество шаблонного кода.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.5, 2.0)
print(p) # Point(x=1.5, y=2.0)атрибуты класса python (атрибуты классов и объектов в python)
Класс Point сразу получает конструктор и строковое представление. Для неизменяемых объектов используется параметр frozen=True.
@dataclass(frozen=True)
class ImmutablePoint:
x: float
y: float
p = ImmutablePoint(1, 2)
# p.x = 3 # Ошибка: cannot assign to fieldбиблиотека классов python (библиотека классов в python)
Типичная ошибка: изменяемые значения по умолчанию, такие как пустой список, приводят к неожиданному поведению. Модуль предотвращает это, требуя использовать field(default_factory=list).
from dataclasses import field
@dataclass
class Group:
members: list = field(default_factory=list)метод объекта python (методы объектов в python)
Как сделать класс данных с возможностью изменять поля после создания?
По умолчанию экземпляры изменяемы. Если требуется защита от изменений, используйте frozen=True. Для частичной защиты можно создать методы, проверяющие условия, но это выходит за рамки класса данных.
@dataclass
class Mutable:
value: int
obj = Mutable(10)
obj.value = 20 # работаетPython структура объекта (структура объекта в python)
Как создать простой класс без автоматических методов?
Без @dataclass приходится писать __init__ вручную. Это полезно для понимания внутреннего устройства, но увеличивает код.
class ManualPoint:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self):
return f'ManualPoint(x={self.x!r}, y={self.y!r})'
p = ManualPoint(1, 2)
print(p) # ManualPoint(x=1, y=2)Python создание объектов (создание объектов в python)
Проблема: легко ошибиться в написании __repr__ или забыть добавить __eq__. Классы данных решают это единообразно.
Как использовать именованные кортежи для хранения данных?
typing.NamedTuple создаёт неизменяемые объекты с автоматическими методами, но без методов класса. Подходит для простых записей.
from typing import NamedTuple
class PointNT(NamedTuple):
x: float
y: float
p = PointNT(1.5, 2.0)
print(p.x) # 1.5
# p.x = 2.0 # ОшибкаSelf object python (объект self в python)
Ограничение: поля упорядочены (кортеж), нет возможности добавить методы, изменяемость отсутствует. Для более сложной логики лучше подходит @dataclass.
Как получить расширенные возможности валидации и сериализации?
Библиотека pydantic предоставляет классы данных с валидацией типов и автоматической сериализацией в JSON. Это популярно в веб-разработке.
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
user = User(name='Alice', age=30)
print(user.json()) # {"name": "Alice", "age": 30}Отличие: Pydantic выполняет проверку и преобразование типов при создании объекта, что может замедлить работу, но даёт надёжность. Для простых DTO лучше использовать стандартный @dataclass.
Дополнительные примеры работы с классами данных
Наследование и порядок полей
При наследовании порядок полей определяется по MRO. Поля базового класса идут первыми.
@dataclass
class Base:
x: int = 0
@dataclass
class Derived(Base):
y: int = 0
d = Derived(1, 2)
print(d) # Base(x=1, y=2)Base(x=1, y=2)
Использование field() для настройки полей
Функция field позволяет задать поведение: значения по умолчанию, фабрики, исключение из __repr__, сравнения.
from dataclasses import field
@dataclass
class Config:
name: str
version: int = field(default=1, repr=False)
data: list = field(default_factory=list, compare=False)
c1 = Config('app')
c2 = Config('app', data=[1,2])
print(c1 == c2) # True (data не участвует в сравнении)True
Метод __post_init__ для пост-обработки
После выполнения __init__ вызывается __post_init__, если он определён. Полезно для валидации или вычисляемых полей.
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False)
def __post_init__(self):
self.area = self.width * self.height
r = Rectangle(3, 4)
print(r) # Rectangle(width=3, height=4, area=12)Rectangle(width=3, height=4, area=12)
Слоты для экономии памяти
Параметр slots=True (Python 3.10+) создаёт слоты, ускоряя доступ и уменьшая память.
@dataclass(slots=True)
class Point:
x: int
y: int
p = Point(1, 2)
print(p.__slots__) # ('x', 'y')('x', 'y')Изменяемые и неизменяемые объекты: сравнение
Для frozen=True объекты становятся хешируемыми (если поля хешируемы), их можно использовать как ключи словаря.
@dataclass(frozen=True)
class Frost:
a: int
s = {Frost(1): "one"}
print(s) # {Frost(a=1): 'one'}{Frost(a=1): 'one'}Обычные классы данных по умолчанию не хешируются (если не задано frozen=True или указан __hash__).
Сравнение с NamedTuple
from typing import NamedTuple
@dataclass
class DC:
x: int
y: int = 10
class NT(NamedTuple):
x: int
y: int = 10
# Создание
print(DC(1)) # DC(x=1, y=10)
print(NT(1)) # NT(x=1, y=10)
# Изменяемость
dc = DC(1)
dc.x = 5
print(dc) # DC(x=5, y=10)
# В NamedTuple изменить нельзя