Классы данных против типизации.

Короче говоря

PEP-557 представил классы данных в стандартной библиотеке Python, которые в основном могут заполнять та же роль, что и collections.namedtuple и typing.NamedTuple. И теперь мне интересно, как разделить варианты использования, в которых namedtuple по-прежнему является лучшим решением.

Преимущества классов данных перед NamedTuple

Конечно, вся заслуга dataclass, если нам понадобится:

  • изменяемые объекты
  • поддержка наследования
  • property декораторы, управляемые атрибуты
  • сгенерированные определения методов из коробки или настраиваемые определения методов

Преимущества классов данных кратко описаны в том же PEP: Почему бы просто не использовать namedtuple.

В: В каких случаях namedtuple по-прежнему является лучшим выбором?

Но как насчет противоположного вопроса для именованных кортежей: почему бы просто не использовать класс данных? Я думаю, что namedtuple лучше с точки зрения производительности, но пока не нашел подтверждения этому.

Пример

Рассмотрим следующую ситуацию:

Мы собираемся хранить размеры страниц в небольшом контейнере со статически определенными полями, подсказками типов и именованным доступом. Никакого дальнейшего хеширования, сравнения и т. Д. Не требуется.

Подход NamedTuple:

from typing import NamedTuple

PageDimensions = NamedTuple("PageDimensions", [('width', int), ('height', int)])

Подход DataClass:

from dataclasses import dataclass

@dataclass
class PageDimensions:
    width: int
    height: int

Какое решение предпочтительнее и почему?

P.S. вопрос никоим образом не дублирует тот, потому что здесь я спрашиваю о случаях , в котором namedtuple лучше, а не о разнице (я проверил документы и источники, прежде чем спрашивать)


person Oleh Rybalchenko    schedule 03.08.2018    source источник
comment
Возможный дубликат Что такое классы данных и чем они отличаются от обычных классов?   -  person scharette    schedule 03.08.2018
comment
Я видел этот вопрос, но нет ответа по главному: в каких случаях именованные кортежи все же лучше использовать?   -  person Oleh Rybalchenko    schedule 03.08.2018
comment
См. Также stackoverflow.com/questions / 3357581 /   -  person pylang    schedule 22.12.2018
comment
Обратите внимание, что использование списка NamedTuple в качестве входных данных для np.array будет работать, потому что (как указано в принятом ответе) NamedTuple наследуется от tuple. Numpy не так гладко обрабатывает классы данных (рассматривая их как имеющие dtype object).   -  person Jasha    schedule 28.07.2021


Ответы (3)


Это зависит от ваших потребностей. У каждого из них есть свои преимущества.

Вот хорошее объяснение классов данных на PyCon 2018 Раймонд Хеттингер - классы данных: генератор кода для завершить все генераторы кода

В Dataclass вся реализация написана на Python, тогда как в NamedTuple все эти варианты поведения предоставляются бесплатно, поскольку NamedTuple наследуется от tuple. И поскольку структура tuple написана на C, стандартные методы работают быстрее в NamedTuple (хеширование, сравнение и т. Д.).

Также обратите внимание, что Dataclass основан на dict, тогда как NamedTuple основан на tuple. Таким образом, у вас есть преимущества и недостатки использования этих структур. Например, использование пространства меньше с NamedTuple, но доступ по времени быстрее с Dataclass.

Пожалуйста, посмотрите мой эксперимент:

In [33]: a = PageDimensionsDC(width=10, height=10)

In [34]: sys.getsizeof(a) + sys.getsizeof(vars(a))
Out[34]: 168

In [35]: %timeit a.width
43.2 ns ± 1.05 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [36]: a = PageDimensionsNT(width=10, height=10)

In [37]: sys.getsizeof(a)
Out[37]: 64

In [38]: %timeit a.width
63.6 ns ± 1.33 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Но с увеличением количества атрибутов NamedTuple время доступа остается таким же небольшим, потому что для каждого атрибута создается свойство с именем атрибута. Например, в нашем случае часть пространства имен нового класса будет выглядеть так:

from operator import itemgetter

class_namespace = {
...
    'width': property(itemgetter(0, doc="Alias for field number 0")),
    'height': property(itemgetter(0, doc="Alias for field number 1"))**
}

В каких случаях namedtuple по-прежнему является лучшим выбором?

Когда ваша структура данных должна / может быть неизменной, хешируемой, повторяемой, распаковываемой, сопоставимой, тогда вы можете использовать NamedTuple. Если вам нужно что-то посложнее, например возможность наследования для вашей структуры данных, используйте Dataclass.

person Oleksandr Yarushevskyi    schedule 03.08.2018
comment
Я согласен с этим ответом. В моем случае я использую NamedTuple путем ввода, если это возможно, потому что его можно распаковать и распространить. Однако есть много случаев, когда мне нужен класс данных, обычно из-за наследования или настраиваемого init. - person Howard Lovatt; 02.06.2020
comment
Мне интересно, что оба dataclasses.Dataclass и collections.namedtuple являются просто генераторами кода. обаятельный. в случае collections.namedtuple он имеет огромный строковый литерал шаблона, который получает exec. Я думал, что они собираются как-то программно все это создать. но генерация кода затем имеет смысл. - person Trevor Boyd Smith; 03.11.2020
comment
FWIW, классы данных также могут быть неизменяемыми, хешируемыми, повторяемыми и сопоставимыми. Декоратор dataclass() принимает kwarg frozen=True. - person ffledgling; 18.04.2021

В программировании в целом все, что МОЖЕТ быть неизменным, ДОЛЖНО быть неизменным. Мы получаем две вещи:

  1. Проще читать программу - нам не нужно беспокоиться об изменении значений, после создания экземпляра он никогда не изменится (namedtuple)
  2. Меньше шансов на странные ошибки

Вот почему, если данные неизменяемы, вы должны использовать именованный кортеж вместо класса данных.

Я написал это в комментарии, но упомяну здесь: вы определенно правы, что есть совпадение, особенно с frozen=True в классах данных, но есть еще такие функции, как распаковка, принадлежащая именованным кортежам, и она всегда неизменна - Я сомневаюсь, что они удалят именованные кортежи как таковые

person maor10    schedule 03.08.2018
comment
Почему не использовать класс данных с @dataclass(frozen=True)? - person ; 03.08.2018
comment
Еще одно преимущество - распаковка по именованным кортежам, например если у меня есть Point (x, y), я могу распаковать его x, y = point- - person maor10; 03.08.2018
comment
Я хочу прояснить, что вы правы в некотором смысле - именованные кортежи были созданы до python3, и, очевидно, здесь есть некоторое совпадение. Но поскольку это не точная замена (распаковка, именованные кортежи всегда неизменяемы), они, вероятно, не будут удалять именованные кортежи. - person maor10; 03.08.2018
comment
@ maor10 спасибо за ответ, распаковка действительно единственное преимущество, которое я пока не вижу. Как упоминалось выше, класс данных может быть неизменным. - person Oleh Rybalchenko; 03.08.2018
comment
Думаю, вы можете немного переписать ответ, чтобы он был понятен остальным и принял его. Кажется, что сама по себе неизменность - это НЕ дело, в основном речь идет о распаковке. - person Oleh Rybalchenko; 03.08.2018

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

https://shayallenhill.com/python-struct-options/

Суть в том, что namedtuple лучше распаковывать, разбирать и определять размер. Dataclass быстрее и гибче.

Namedtuple также отлично подходит для мягкой печати, когда вы хотите вместо этого передать кортеж. Определить тип

class CircleArg(NamedTuple):
    x: float
    y: float
    radius: float

Затем распакуйте его в свои functions. Не используйте .attributes, и вы получите красивую подсказку типа без PITA для вызывающего.

*focus, radius = circle_arg_instance (or tuple)

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

person Shay    schedule 20.12.2019
comment
Я бы не стал реорганизовывать стабильный код, чтобы переходить от одного к другому. - Мудрый разработчик - person rodrigo-silveira; 02.07.2021