数据类与键入.NamedTuple 主要用例

长话短说

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

数据类优于 NamedTuple

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

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

在同一个 PEP: 为什么不直接用 namedtuple 呢中简要说明了数据类的优点。

问: 在哪些情况下,命名元组仍然是更好的选择?

但是,对于命名元组来说,有一个相反的问题: 为什么不仅仅使用数据类呢? 从性能的角度来看,我猜可能名称 tuple 更好,但是还没有找到确认。

例子

让我们考虑以下情况:

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

NamedTuple 方法:

from typing import NamedTuple


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

DataClass 方法:

from dataclasses import dataclass


@dataclass
class PageDimensions:
width: int
height: int

哪种解决方案更好? 为什么?

另外,这个问题在任何方面都不是 那个的复制品,因为在这里我问的是关于 案子的,其中 namedtuple 更好,而不是关于 区别(在问之前我已经检查了文档和源代码)

56051 次浏览

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

  1. 更容易阅读程序-我们不需要担心值的改变,一旦它被实例化,它将永远不会改变(namedtuple)
  2. 更不容易有奇怪的虫子

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

我把它写在评论里了,但我会在这里提到: 你肯定是对的,有一个重叠,特别是与 frozen=True在数据类-但仍然有一些功能,如解包属于命名元组,它总是不可变的-我怀疑他们会删除命名元组作为这样的

这取决于你的需要。他们每个人都有自己的利益。

下面是对 PyCon 2018Raymond Hettinger-数据类: 结束所有代码生成器的代码生成器上的数据类的一个很好的解释

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

请注意,ABC0是基于 dictABC2是基于 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"))**
}

在哪些情况下,命名为 tuple 仍然是更好的选择?

当你的数据结构需要/可以是 不可变的,散列的,可迭代的,不可打包的,可比较的,那么你可以使用 NamedTuple。如果你的数据结构需要一些 更复杂的,例如,继承的可能性,那么使用 Dataclass

我有同样的问题,所以运行了一些测试并在这里记录它们: 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

对我来说,一个用例是不支持 dataclasses的框架。特别是 张量流。在这里,tf.function可以与 typing.NamedTuple一起工作,但与 dataclass不能。

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


@tf.function
def train_step(self, my_fancy_data: MyFancyData):
...

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

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


TypeError: Multiple inheritance with NamedTuple is not supported

我没有看到任何其他的答案提到它,但在我看来,最重要的区别之一是如何平等和比较的工作。比较命名元组时,会忽略名称: 如果两个命名元组包含相同的值,且顺序相同,则它们是相等的,即使它们具有不同的类名或字段名:

>>> from collections import namedtuple
>>> A = namedtuple('A', ())
>>> B = namedtuple('B', ())
>>> a = A()
>>> b = B()
>>> a == b
True

另一方面,数据类实例只有在属于相同类型时才被认为是相等的。我几乎总是想要后一种行为: 我希望不同类型的事物是截然不同的。