数据类与typing.NamedTuple的主要用途

250

长话短说

PEP-557 向 Python 标准库引入了数据类,其基本上可以扮演与collections.namedtupletyping.NamedTuple相同的角色。现在我在想如何区分仍然更适合使用namedtuple的用例。

Data classes 相对 NamedTuple 的优势

当然,如果我们需要:

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

所有的优势归功于dataclass,同样的这些优势在同一 PEP 中得到了简要解释:为什么不仅仅使用namedtuple.

Q: 在什么情况下,namedtuple 仍然是更好的选择?

但对于namedtuples,反过来问一个相反的问题:为什么不仅仅使用dataclass? 我猜可能从性能的角度来看,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更好的情况,而不是它和其他方式的区别(在提问之前我已经检查了文档和来源)


2
我看到了那个问题,但是没有回答主要问题:在哪些情况下仍然更好地使用namedtuples? - Oleh Rybalchenko
请参见 https://dev59.com/V3A75IYBdhLWcg3wUHWQ。 - pylang
1
请注意,使用 NamedTuple 列表作为 np.array 的输入将“正常工作”,因为(如接受的答案中所述)NamedTuple 继承自 tuple。Numpy 不能像处理 dtypeobject 的数据类一样平稳地处理它们。 - Jasha
初学者简介:选择数据类。 - OrenIshShalom
1
值得注意的是,NamedTuples 在子类化方面存在问题 - Stevoisiak
7个回答

183

这取决于您的需求。它们各自都有自己的好处。

以下是PyCon 2018中Dataclasses的良好解释Raymond Hettinger - Dataclasses: The code generator to end all code generators

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


8
我同意这个回答。对于我的情况,如果可能的话,我会使用typing中的NamedTuple,因为它可以被拆开和扩展。然而,在许多情况下我需要使用dataclass,通常是因为需要继承或自定义初始化。 - Howard Lovatt
1
我觉得有趣的是,dataclasses.Dataclasscollections.namedtuple都只是代码生成器。在collections.namedtuple的情况下,它有一个巨大的模板字符串字面量,会被执行。我以为他们会以某种方式通过编程来创建所有这些内容,但代码生成然后执行是有道理的。 - Trevor Boyd Smith
43
值得一提的是,数据类也可以是不可变、可哈希、可迭代和可比较的。 dataclass()装饰器接受一个frozen=True的关键字参数,用于指定数据类是否为不可变类型。 - ffledgling
3
为什么字典访问属性的时间比元组少? - WeiChing 林煒清
1
typing.NamedTuple 中,您可以(也必须)指定属性的类型。 - Jacktose
显示剩余3条评论

43

在编程中,一切可以是不可变的就应该是不可变的。这样我们可以得到两件事情:

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

因此,如果数据是不可变的,应该使用命名元组而不是数据类

我已经在评论中提到了,但我还想在这里提一下: 你确实是对的,特别是在数据类中使用frozen=True的时候会有重叠之处,但是还有像是解包等属于命名元组的特性,而且它始终是不可变的-我怀疑他们不会删除命名元组这种东西。


52
为什么不使用带有@dataclass(frozen=True)的数据类? - user2201041
12
另一个优点是在具名元组中进行解包——例如,如果我有一个 Point(x, y),我可以通过 x, y = point 来解包它。 - maor10
2
我想要澄清一下,你的观点在某种程度上是正确的——namedtuples是在Python3之前创建的,这里显然有一些重叠。但是因为它不是一个完全的替代品(解包、namedtuples始终是不可变的),所以他们可能不会删除namedtuples。 - maor10
4
@maor10 感谢你的回答,目前我唯一看到的优势就是拆包。如上所述,数据类可以是不可变的。 - Oleh Rybalchenko
1
我认为你可以稍微重写一下答案,以便让其他人更清楚地理解并接受它。似乎不是不可变性本身的问题,主要是关于解包的问题。 - Oleh Rybalchenko

37

我曾经有同样的问题,所以进行了一些测试,并在这里记录下来:https://shayallenhill.com/python-struct-options/

总结:

  • 对于解包、展开和大小,NamedTuple 更好。
  • DataClass 更快速和更灵活。
  • 差异不是很大,如果已有稳定的代码,我不会为了从一个类转移到另一个类而重构代码。
  • NamedTuple 也非常适用于软类型化,当你想要传递一个元组时。

要做到这一点,可以定义一个继承自它的类型...

from typing import NamedTuple

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

...然后在您的函数内部解压缩它。不要使用.attributes,这样您就可以获得一个漂亮的“类型提示”,而无需为调用方带来任何麻烦。

*focus, radius = circle_arg_instance  # or tuple

30
一位聪明的开发者说:“我不会重构稳定的代码来从一个东西移动到另一个东西。” - rodrigo-silveira
or tuple 参数语法的目的是什么? - WestCoastProjects
@WestCoastProjects,只是我格式不好。现在已经更新了。这行代码只是想表达你可以输入a)一个CircleArg类的实例或b)一个普通的3元组...在=号右侧。 - Shay
NamedTuple在除了属性访问之外的所有指标上都比DataClass快得多,而在属性访问方面的差异相对较小。当涉及到实例创建、排序和哈希时,如果性能是一个主要考虑因素,NamedTuple绝对是最佳选择。 - undefined

34

我没有看到其他回答中提到,但是我认为最重要的区别之一与平等和比较的工作方式有关。当您比较命名元组时,名称会被忽略:如果它们包含相同顺序的相同值,即使它们有不同的类名或字段名,两个命名元组也是相等的:

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

另一方面,数据类实例仅在它们是相同类型时才被视为相等。我基本上总是希望后者的行为:我期望不同类型的对象是不同的。


1
好观点,从未考虑过你提到的方面,谢谢。 - Oleh Rybalchenko

14

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

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

TypeError: Multiple inheritance with NamedTuple is not supported

3
这实际上是一个 bug。它应该在 Python 3.11 中得到修复(https://github.com/python/cpython/pull/92027)。 - rmorshea

8

对我来说,一个应用场景是一些不支持dataclasses的框架。特别是TensorFlow。在那里,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):
    ...

0
在它们之间还有一个小差别,迄今为止没有提到过。 命名元组的属性可以通过它们的名称和索引访问,而数据类的属性只能通过它们的属性名称访问。当我对对象列表进行排序时,遇到了这个差异。
对于命名元组,我们可以同时使用itemgetterattrgetter辅助函数。对于数据类,我们只能使用attrgetter函数。
#!/usr/bin/python

from typing import NamedTuple
from operator import itemgetter, attrgetter
# from dataclasses import dataclass

# @dataclass(frozen=True)
# class City:
#     cid: int
#     name: str
#     population: int

class City(NamedTuple):
    cid: int
    name: str
    population: int

c1 = City(1, 'Bratislava', 432000)
c2 = City(2, 'Budapest', 1759000)
c3 = City(3, 'Prague', 1280000)
c4 = City(4, 'Warsaw', 1748000)
c5 = City(5, 'Los Angeles', 3971000)
c6 = City(6, 'Edinburgh', 464000)
c7 = City(7, 'Berlin', 3671000)

cities = [c1, c2, c3, c4, c5, c6, c7]

sorted_cities = sorted(cities, key=attrgetter('name'))

for city in sorted_cities:
    print(city)

print('---------------------')

sorted_cities = sorted(cities, key=itemgetter(2))

for city in sorted_cities:
    print(city)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接