长话短说
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更好的情况,而不是关于差异(在问之前我已经检查了文档和源代码)
这取决于你的需要。他们每个人都有自己的好处。
以下是PyCon 2018上关于数据类的一个很好的解释:数据类:结束所有代码生成器的代码生成器
在Dataclass中,所有实现都是用Python编写的,而在NamedTuple中,所有这些行为都是免费的,因为NamedTuple继承自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。
我也有同样的问题,所以进行了一些测试,并记录在这里: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