Что такое классы данных и как они отличаются от обычных классов?
С помощью PEP 557 классы данных вводятся в стандартную библиотеку Python.
Они используют декоратор @dataclass
и они должны быть "изменяемыми именованными кортами по умолчанию", но я не совсем уверен, что понимаю, что это на самом деле означает и чем они отличаются от обычных классов.
Что такое классы данных Python и когда лучше их использовать?
Ответы
Ответ 1
Классы данных - это просто обычные классы, которые ориентированы на сохранение состояния и содержат больше логики. Каждый раз, когда вы создаете класс, который в основном состоит из атрибутов, вы создали класс данных.
Модуль dataclasses
облегчает создание классов данных. Он заботится о многих котельных плитах для вас.
Это особенно важно, когда ваш класс данных должен быть хешируемым; для этого требуется метод __hash__
а также метод __eq__
. Если вы добавите пользовательский метод __repr__
для простоты отладки, он может стать довольно многословным:
class InventoryItem:
'''Class for keeping track of an item in inventory.'''
name: str
unit_price: float
quantity_on_hand: int = 0
def __init__(
self,
name: str,
unit_price: float,
quantity_on_hand: int = 0
) -> None:
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
def __repr__(self) -> str:
return (
'InventoryItem('
f'name={self.name!r}, unit_price={self.unit_price!r}, '
f'quantity_on_hand={self.quantity_on_hand!r})'
def __hash__(self) -> int:
return hash((self.name, self.unit_price, self.quantity_on_hand))
def __eq__(self, other) -> bool:
if not isinstance(other, InventoryItem):
return NotImplemented
return (
(self.name, self.unit_price, self.quantity_on_hand) ==
(other.name, other.unit_price, other.quantity_on_hand))
С помощью dataclasses
вы можете уменьшить его до:
from dataclasses import dataclass
@dataclass(unsafe_hash=True)
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
Тот же декоратор класса также может генерировать методы сравнения (__lt__
, __gt__
и т.д.) И обрабатывать неизменность.
namedtuple
также являются классами данных, но являются неизменяемыми по умолчанию (а также являются последовательностями). dataclasses
данных гораздо более гибки в этом отношении и могут быть легко структурированы так, чтобы они могли выполнять ту же роль, что и класс namedtuple
.
PEP был вдохновлен проектом attrs
, который может сделать еще больше (включая слоты, валидаторы, конвертеры, метаданные и т.д.).
Если вы хотите увидеть некоторые примеры, я недавно использовал dataclasses
для нескольких своих решений Advent of Code, посмотрите решения для 7-го, 8-го, 11-го и 20-го дня.
Если вы хотите использовать модуль dataclasses
в версиях Python <3.7, вы можете установить модуль backported (требуется 3.6) или использовать проект attrs
упомянутый выше.
Ответ 2
обзор
Вопрос был решен. Тем не менее, этот ответ добавляет некоторые практические примеры, которые помогут в базовом понимании классов данных.
Что такое классы данных Python и когда лучше их использовать?
- генераторы кода: генерировать шаблонный код; Вы можете реализовать специальные методы в обычном классе или автоматически назначить их классу данных.
- контейнеры данных: структуры, которые содержат данные (например, кортежи и слова), часто с точками, с доступом к атрибутам, таким как классы,
namedtuple
и другие.
msgstr "изменяемые именованные кортежи по умолчанию [s]"
Вот что означает последняя фраза:
- mutable: по умолчанию атрибуты класса данных могут быть переназначены. При желании вы можете сделать их неизменяемыми (см. Примеры ниже).
- namedtuple: у вас есть точки, доступ к атрибутам, как
namedtuple
или обычный класс. - default: вы можете назначить значения по умолчанию атрибутам
По сравнению с обычными классами, вы в основном экономите на наборе стандартного кода.
Характеристики
Вот обзор возможностей класса данных (см. Примеры в сводной таблице).
Что вы получаете
Вот функции, которые вы получаете по умолчанию из классов данных.
Атрибуты + Представление + Сравнение
import dataclasses
@dataclasses.dataclass
#@dataclasses.dataclass() # alternative
class Color:
r : int = 0
g : int = 0
b : int = 0
Следующие значения по умолчанию автоматически устанавливаются в True
:
@dataclasses.dataclass(init=True, repr=True, eq=True)
Что вы можете включить
Дополнительные функции доступны, если для соответствующих ключевых слов установлено значение True
.
порядок
@dataclasses.dataclass(order=True)
class Color:
r : int = 0
g : int = 0
b : int = 0
Методы упорядочения теперь реализованы (операторы перегрузки: < > <=>=
), аналогично functools.total_ordering
с более строгими тестами на равенство.
Hashable, изменчивый
@dataclasses.dataclass(unsafe_hash=True) # override base '__hash__'
class Color:
...
Хотя объект потенциально изменчив (возможно, нежелателен), реализован хэш.
Hashable, неизменный
@dataclasses.dataclass(frozen=True) # 'eq=True' (default) to be immutable
class Color:
...
Теперь реализован хеш, и изменение объекта или присвоение атрибутам запрещено.
В целом, объект может быть хэшируемым, если unsafe_hash=True
или frozen=True
.
Смотрите также оригинальную таблицу логики хеширования с более подробной информацией.
Что ты не получаешь
Чтобы получить следующие возможности, специальные методы должны быть реализованы вручную:
Unpackable
@dataclasses.dataclass
class Color:
r : int = 0
g : int = 0
b : int = 0
def __iter__(self):
yield from dataclasses.astuple(self)
оптимизация
@dataclasses.dataclass
class SlottedColor:
__slots__ = ["r", "b", "g"]
r : int
g : int
b : int
Размер объекта теперь уменьшен:
>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888
В некоторых случаях __slots__
также повышает скорость создания экземпляров и доступа к атрибутам. Кроме того, слоты не позволяют назначений по умолчанию; в противном случае возникает ValueError
.
Подробнее о слотах читайте в этом блоге.
Таблица результатов
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Feature | Keyword | Example | Implement in a Class |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes | init | Color().r -> 0 | __init__ |
| Representation | repr | Color() -> Color(r=0, g=0, b=0) | __repr__ |
| Comparision* | eq | Color() == Color(0, 0, 0) -> True | __eq__ |
| | | | |
| Order | order | sorted([Color(0, 50, 0), Color()]) -> ... | __lt__, __le__, __gt__, __ge__ |
| Hashable | unsafe_hash/frozen | {Color(), {Color()}} -> {Color(r=0, g=0, b=0)} | __hash__ |
| Immutable | frozen + eq | Color().r = 10 -> TypeError | __setattr__, __delattr__ |
| | | | |
| Unpackable+ | - | r, g, b = Color() | __iter__ |
| Optimization+ | - | sys.getsizeof(SlottedColor) -> 888 | __slots__ |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
+ Эти методы не генерируются автоматически и требуют ручной реализации в классе данных.
*__ne__
не реализован.
Дополнительные возможности
После инициализации
@dataclasses.dataclass
class RGBA:
r : int = 0
g : int = 0
b : int = 0
a : float = 1.0
def __post_init__(self):
self.a : int = int(self.a * 255)
RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)
наследование
@dataclasses.dataclass
class RGBA(Color):
a : int = 0
Конверсии
Преобразовать класс данных в кортеж или dict, рекурсивно:
>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}
Ограничения
Рекомендации
- Р. Хеттингер говорит о Dataclasses: генератор кода для завершения всех генераторов кода
- Т. Ханнер говорит о более простых классах: классы Python без всякой лжи
- Документация Python по деталям хэширования
- Реальное руководство по Python на Ultimate Guide по классам данных в Python 3.7
- Сообщение в блоге A. Shaw Краткий обзор классов данных Python 3.7
- Э. Смит github хранилище для классов данных
Ответ 3
Btw. Раймон Хеттингер (Raymond Hettinger), разработчик ядра Python, выступил с большой речью на PyCon 2018:
https://www.youtube.com/watch?v=T-TwcmT6Rcw&t=1390
Слайды здесь: https://twitter.com/raymondh/status/995693882812915712
![comparison]()
Ответ 4
Из спецификация PEP:
Предусмотрен класс декоратора, который проверяет определение класса для переменные с аннотациями типов, как определено в PEP 526, "Синтаксис для Variable Annotations". В этом документе такие переменные называются поля. Используя эти поля, декоратор добавляет сгенерированный метод определения для класса для поддержки инициализации экземпляра, методы сравнения и, необязательно, другие способы, описанные в Раздел спецификации. Такой класс называется классом данных, но там действительно ничего особенного в классе: декоратор добавляет генерирует методы для класса и возвращает тот же класс, что и учитывая.
Генератор @dataclass
добавляет методы к классу, который вы иначе определили бы как __repr__
, __init__
, __lt__
и __gt__
.
Ответ 5
Рассмотрим этот простой класс Foo
from dataclasses import dataclass
@dataclass
class Foo:
def bar():
pass
Вот сравнение dir()
. С левой стороны находится Foo
без декоратора @dataclass, а справа - декоратор @dataclass.
![enter image description here]()
Вот еще одна разница, после использования модуля inspect
для сравнения.
![enter image description here]()