typing
模块文档说下面的两个代码片段是等效的:
from typing import NamedTuple
class Employee(NamedTuple):
name: str
id: int
和:
from collections import namedtuple
Employee = namedtuple('Employee', ['name', 'id'])
它们是完全相同的东西吗?如果不是,两种实现之间有什么区别?
子类化
typing.NamedTuple
生成的类型相当于 collections.namedtuple
,但添加了 __annotations__
、_field_types
和 _field_defaults
属性。出于所有实际目的,生成的代码的行为都是相同的,因为 Python 中当前没有任何内容作用于那些与类型相关的属性(不过,您的 IDE 可能会使用它们)。
作为开发人员,为命名元组使用
typing
模块可以实现更自然的声明式界面:
collections.namedtuple
有了新的defaults
关键字所以这不再是一个优势)和以前一样,您的类将是
tuple
的子类,并且实例将像往常一样是 tuple
的实例。 有趣的是,你的类不会是 NamedTuple
的子类。如果您想知道原因,请继续阅读有关实施细节的更多信息。
from typing import NamedTuple
class Employee(NamedTuple):
name: str
id: int
>>> issubclass(Employee, NamedTuple)
False
>>> isinstance(Employee(name='guido', id=1), NamedTuple)
False
typing.NamedTuple
是一个类,它使用metaclasses和自定义__new__
来处理注释,然后它委托给collections.namedtuple
来构建并返回类型。正如您可能从小写名称约定中猜到的那样,collections.namedtuple
不是类型/类 - 它是工厂函数。 它的工作原理是构建一个 Python 源代码字符串,然后对该字符串调用 exec
。 生成的构造函数从命名空间中取出,并包含在元类type
的3参数调用中以构建并返回您的类。这解释了上面看到的奇怪的继承破坏,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
进行多重继承(它一开始就无法正常工作)。有关更改,请参阅
namedtuple()无法设置字段的类型,而NamedTuple的字段必须具有如下所示的类型:
from collections import namedtuple
from typing import NamedTuple
Person = namedtuple(typename='Person', field_names=['name', 'age'])
class Person(NamedTuple):
name: str # Here
age: int # Here
p = Person(name='John', age=36)
print(p, p.name, p.age) # Person(name='John', age=36) John 36