namedtuple和NamedTuple有什么区别?

100

typing 模块文档 表明以下两个代码片段是等效的。

from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    id: int

并且

from collections import namedtuple

Employee = namedtuple('Employee', ['name', 'id'])

它们是完全相同的吗?如果不是,这两种实现有什么区别?
1个回答

97

通过子类化typing.NamedTuple生成的类型等同于collections.namedtuple,但添加了__annotations___field_types_field_defaults属性。就所有实际目的而言,生成的代码将表现相同,因为Python中当前没有任何与这些类型相关的属性(尽管您的IDE可能会使用它们)。

作为开发人员,使用typing模块来命名元组允许更自然的声明式接口:

  • 您可以轻松指定字段的默认值(编辑:在Python 3.7中,collections.namedtuple获得了新的defaults关键字,因此这不再是一个优点
  • 您不需要重复类型名称两次(“Employee”)
  • 您可以直接自定义类型(例如添加文档字符串或一些方法)

像以前一样,你的类将是tuple的子类,实例将像往常一样是tuple的实例。有趣的是,你的类将不是NamedTuple的子类。如果想知道原因,请继续阅读有关实现细节的更多信息。

from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    id: int

Python 3.8及以下版本的行为

>>> issubclass(Employee, NamedTuple)
False
>>> isinstance(Employee(name='guido', id=1), NamedTuple)
False

typing.NamedTuple是一个类,它使用元类和自定义的__new__来处理注释,然后将其委托给collections.namedtuple构建并返回类型。从小写名称约定可以猜测出,collections.namedtuple不是一个类型/类 - 它是一个工厂函数。它通过构建Python源代码字符串,然后在此字符串上调用exec来工作。生成的构造函数从命名空间中取出,并包含在对元类type的三参数调用中,以构建并返回您的类。这解释了上面看到的怪异继承断裂,NamedTuple使用一个元类来使用一个不同的元类来实例化类对象。

Python 3.9中的行为

typing.NamedTuple从类型(class)更改为函数(def

>>> issubclass(Employee, NamedTuple)
TypeError: issubclass() arg 2 must be a class or tuple of classes
>>> isinstance(Employee(name="guido", id=1), NamedTuple)
TypeError: isinstance() arg 2 must be a type or tuple of types

元类技巧已经消失了,现在只是一个简单的工厂函数,它调用collections.namedtuple,然后在返回的类型上设置__annotations__。现在禁止使用NamedTuple进行多重继承(一开始就无法正常工作)。
请参见bpo40185 / GH-19371以获取更多信息。

由于它是一个类,你还可以传递默认值,这是从collections namedtuple中无法做到的。 - dfundako
2
@dfundako 可以使用 collections.namedtuple,通过子类化生成的类型来实现。但是 typing.NamedTuple 提供了更简单的接口。 - wim

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