NamedTuple 和 NamedTuple 有什么区别?

typing模块文档表示下面的两个代码片段是等价的。

from typing import NamedTuple


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

还有

from collections import namedtuple


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

它们是完全相同的吗? 如果不是,那么两种实现之间的差异是什么?

10066 次浏览

通过子类化 typing.NamedTuple生成的类型等同于 collections.namedtuple,但是添加了 __annotations___field_types_field_defaults属性。出于所有实际目的,生成的代码的行为都是相同的,因为当前 Python 中没有任何东西作用于这些类型相关的属性(但您的 IDE 可能会使用它们)。

作为开发人员,对您的 namedtuple 使用 typing模块可以实现更自然的声明性接口:

  • 您可以轻松地为字段指定默认值(在 Python 3.7中,ABC0获得了一个新的 defaults关键字 所以这不再是一个优势)
  • 您不需要重复类型名称两次(“ Employee”)
  • 您可以直接自定义类型(例如,添加 docstring 或某些方法)

与前面一样,您的类将是 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。构建和返回类的 __new__0和 __new__1。这解释了上面看到的奇怪的继承破坏,NamedTuple使用一个元类来使用一个 __new__2元类来实例化类对象。

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

使用 NamedTuple的多重继承现在被禁止使用(因为它一开始就不能正常工作)。

有关更改请参阅 Bpo40185/GH-19371