长话短说

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

数据类优于NamedTuple

当然,如果我们需要,所有的功劳都归于数据类:

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

在同一个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更好的情况,而不是关于差异(在问之前我已经检查了文档和源代码)


当前回答

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

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

另一方面,数据类实例只有在具有相同类型时才会被认为是相等的。我总是希望出现后一种行为:我希望不同类型的事物是不同的。

其他回答

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

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

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

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

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

简介:

NamedTuple更适合解包、爆炸和大小。 DataClass更快更灵活。 区别不是很大,我不会重构稳定的代码从一个移到另一个。 当您希望能够传递一个元组时,NamedTuple也非常适合软输入。

为此,定义一个从它继承的类型…

from typing import NamedTuple

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

...然后在函数中解压缩它。不要使用.attributes,您将得到一个很好的“类型提示”,而无需为调用者提供任何PITA。

*focus, radius = circle_arg_instance  # or tuple

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

另一方面,数据类实例只有在具有相同类型时才会被认为是相等的。我总是希望出现后一种行为:我希望不同类型的事物是不同的。

对我来说,一个用例是不支持数据类的框架。特别是TensorFlow。这是一个tf。函数可以与一个类型一起工作。NamedTuple,但没有数据类。

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

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