130

长话短说

PEP-557将数据类引入 Python 标准库,基本上可以扮演collections.namedtupletyping.NamedTuple. 现在我想知道如何分离 namedtuple 仍然是更好的解决方案的用例。

数据类优于 NamedTuple

当然,dataclass如果我们需要,所有功劳都归于:

  • 可变对象
  • 继承支持
  • property装饰器,可管理的属性
  • 开箱即用的生成方法定义或可自定义的方法定义

数据类的优势在同一个 PEP 中进行了简要说明:Why not just use namedtuple

问:在哪些情况下,namedtuple 仍然是更好的选择?

但是对于 namedtuples 有一个相反的问题怎么样:为什么不直接使用数据类呢?我猜从性能的角度来看,namedtuple 可能更好,但还没有发现任何确认。

例子

让我们考虑以下情况:

我们将把页面维度存储在一个带有静态定义字段、类型提示和命名访问的小容器中。不需要进一步的散列、比较等。

NamedTuple 方法:

from typing import NamedTuple

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

数据类方法:

from dataclasses import dataclass

@dataclass
class PageDimensions:
    width: int
    height: int

哪种解决方案更可取,为什么?

PS这个问题无论如何都不是那个问题的重复,因为在这里我问的是namedtuple更好的情况,而不是区别(我在询问之前检查了文档和来源)

4

5 回答 5

100

这取决于您的需求。他们每个人都有自己的好处。

这是 PyCon 2018 Raymond Hettinger 上 Dataclasses 的一个很好的解释 - Dataclasses: The code generator to end all code generators

Dataclass所有实现中都是用 Python 编写的,而在 中NamedTuple,所有这些行为都是免费的,因为NamedTuple继承自tuple. 而且由于tuple结构是用 C 编写的,标准方法在NamedTuple(散列、比较等)方面更快。

另请注意,Dataclass基于dictNamedTuple基于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.

于 2018-08-03T13:39:39.237 回答
23

在一般的编程中,任何可以不可变的东西都应该是不可变的。我们得到两件事:

  1. 更容易阅读程序 - 我们不需要担心值的变化,一旦它被实例化,它就永远不会改变(命名元组)
  2. 出现奇怪错误的机会更少

这就是为什么,如果数据是不可变的,你应该使用命名元组而不是数据类

我在评论中写了它,但我会在这里提到它:你绝对是对的,有重叠,特别是frozen=True在数据类中 - 但仍然有一些特性,例如属于命名元组的解包,它总是不可变的 - 我怀疑他们会像这样删除命名元组

于 2018-08-03T12:06:08.863 回答
22

我有同样的问题,所以运行了一些测试并在此处记录它们:https ://shayallenhill.com/python-struct-options/

概括:

  • NamedTuple 更适合解包、分解和调整大小。
  • DataClass 更快、更灵活。
  • 差异并不大,我不会重构稳定的代码以从一个移动到另一个。
  • 当您希望能够传递元组时,NamedTuple 也非常适合软输入。

为此,请定义一个继承自它的类型...

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

...然后将其解压缩到您的函数中。不要使用.attributes,你会得到一个很好的“类型提示”,而调用者没有任何 PITA。

*focus, radius = circle_arg_instance  # or tuple
于 2019-12-20T11:18:15.753 回答
4

另一个重要的限制NamedTuple是它不能是通用的:

import typing as t
T=t.TypeVar('T')
class C(t.Generic[T], t.NamedTuple): ...

TypeError: Multiple inheritance with NamedTuple is not supported
于 2021-09-11T08:22:28.680 回答
1

对我来说,一个用例是不支持dataclasses. 特别是TensorFlow。在那里, atf.function可以与 a 一起使用,typing.NamedTuple但不能与 a 一起使用dataclass

class MyFancyData(typing.NamedTuple):
  some_tensor: tf.Tensor
  some_other_stuf: ...

@tf.function
def train_step(self, my_fancy_data: MyFancyData):
    ...
于 2021-08-25T14:30:14.760 回答