直接从 typing.NamedTuple 继承时出现奇怪的 MRO 结果
Weird MRO result when inheriting directly from typing.NamedTuple
我很困惑为什么FooBar.__mro__
不像上面两个那样显示<class '__main__.Parent'>
在深入研究 CPython 源代码后,我仍然不知道为什么。
from typing import NamedTuple
from collections import namedtuple
A = namedtuple('A', ['test'])
class B(NamedTuple):
test: str
class Parent:
pass
class Foo(Parent, A):
pass
class Bar(Parent, B):
pass
class FooBar(Parent, NamedTuple):
pass
print(Foo.__mro__)
# prints (<class '__main__.Foo'>, <class '__main__.Parent'>, <class '__main__.A'>, <class 'tuple'>, <class 'object'>)
print(Bar.__mro__)
# prints (<class '__main__.Bar'>, <class '__main__.Parent'>, <class '__main__.B'>, <class 'tuple'>, <class 'object'>)
print(FooBar.__mro__)
# prints (<class '__main__.FooBar'>, <class 'tuple'>, <class 'object'>)
# expecting: (<class '__main__.FooBar'>, <class '__main__.Parent'>, <class 'tuple'>, <class 'object'>)
typing.NamedTuple
的设计并不像普通基础 class 那样。看看当您从 NamedTuple
"inherit" 时,甚至 NamedTuple
本身也不在新的 class 的 MRO 中。它实际上只是设计为 collections.namedtuple
class 工厂的接口,mypy
可以检查。
当您从 NamedTuple
继承时,它的 metaclass 完全忽略任何基础 classes 并通过委托给 collections.namedtuple
创建新的 class,然后填充原始命名空间中的方法、属性和内容。新的 class 将始终直接继承自 tuple
。
这是因为 typing.NamedTuple
不是 真正的 正确类型。它是一个class。但它的唯一目的是利用 meta-class 魔法为您提供一种方便的好方法来定义命名元组类型。命名元组直接派生自 tuple
。
请注意,与大多数其他 classes 不同,
from typing import NamedTuple
class Foo(NamedTuple):
pass
print(isinstance(Foo(), NamedTuple))
打印 False
.
这是因为在 NamedTupleMeta
中本质上是在 class 中反省 __annotations__
最终将其用于 return 通过调用 class 创建的 class =18=]:
def _make_nmtuple(name, types):
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
types = [(n, _type_check(t, msg)) for n, t in types]
nm_tpl = collections.namedtuple(name, [n for n, t in types])
# Prior to PEP 526, only _field_types attribute was assigned.
# Now __annotations__ are used and _field_types is deprecated (remove in 3.9)
nm_tpl.__annotations__ = nm_tpl._field_types = dict(types)
try:
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return nm_tpl
class NamedTupleMeta(type):
def __new__(cls, typename, bases, ns):
if ns.get('_root', False):
return super().__new__(cls, typename, bases, ns)
types = ns.get('__annotations__', {})
nm_tpl = _make_nmtuple(typename, types.items())
...
return nm_tpl
当然,namedtuple
本质上只是创建了一个派生自 tuple
的 class。实际上,您的命名元组 class 在 class 定义语句中派生的任何其他 class 都会被忽略,因为这会破坏通常的 class 机制。它可能感觉不对,在很多方面它是丑陋的,但是 实用性胜过纯粹性。 能够写出像这样的东西是很好和实用的:
class Foo(NamedTuple):
bar: int
baz: str
我很困惑为什么FooBar.__mro__
不像上面两个那样显示<class '__main__.Parent'>
在深入研究 CPython 源代码后,我仍然不知道为什么。
from typing import NamedTuple
from collections import namedtuple
A = namedtuple('A', ['test'])
class B(NamedTuple):
test: str
class Parent:
pass
class Foo(Parent, A):
pass
class Bar(Parent, B):
pass
class FooBar(Parent, NamedTuple):
pass
print(Foo.__mro__)
# prints (<class '__main__.Foo'>, <class '__main__.Parent'>, <class '__main__.A'>, <class 'tuple'>, <class 'object'>)
print(Bar.__mro__)
# prints (<class '__main__.Bar'>, <class '__main__.Parent'>, <class '__main__.B'>, <class 'tuple'>, <class 'object'>)
print(FooBar.__mro__)
# prints (<class '__main__.FooBar'>, <class 'tuple'>, <class 'object'>)
# expecting: (<class '__main__.FooBar'>, <class '__main__.Parent'>, <class 'tuple'>, <class 'object'>)
typing.NamedTuple
的设计并不像普通基础 class 那样。看看当您从 NamedTuple
"inherit" 时,甚至 NamedTuple
本身也不在新的 class 的 MRO 中。它实际上只是设计为 collections.namedtuple
class 工厂的接口,mypy
可以检查。
当您从 NamedTuple
继承时,它的 metaclass 完全忽略任何基础 classes 并通过委托给 collections.namedtuple
创建新的 class,然后填充原始命名空间中的方法、属性和内容。新的 class 将始终直接继承自 tuple
。
这是因为 typing.NamedTuple
不是 真正的 正确类型。它是一个class。但它的唯一目的是利用 meta-class 魔法为您提供一种方便的好方法来定义命名元组类型。命名元组直接派生自 tuple
。
请注意,与大多数其他 classes 不同,
from typing import NamedTuple
class Foo(NamedTuple):
pass
print(isinstance(Foo(), NamedTuple))
打印 False
.
这是因为在 NamedTupleMeta
中本质上是在 class 中反省 __annotations__
最终将其用于 return 通过调用 class 创建的 class =18=]:
def _make_nmtuple(name, types):
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
types = [(n, _type_check(t, msg)) for n, t in types]
nm_tpl = collections.namedtuple(name, [n for n, t in types])
# Prior to PEP 526, only _field_types attribute was assigned.
# Now __annotations__ are used and _field_types is deprecated (remove in 3.9)
nm_tpl.__annotations__ = nm_tpl._field_types = dict(types)
try:
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return nm_tpl
class NamedTupleMeta(type):
def __new__(cls, typename, bases, ns):
if ns.get('_root', False):
return super().__new__(cls, typename, bases, ns)
types = ns.get('__annotations__', {})
nm_tpl = _make_nmtuple(typename, types.items())
...
return nm_tpl
当然,namedtuple
本质上只是创建了一个派生自 tuple
的 class。实际上,您的命名元组 class 在 class 定义语句中派生的任何其他 class 都会被忽略,因为这会破坏通常的 class 机制。它可能感觉不对,在很多方面它是丑陋的,但是 实用性胜过纯粹性。 能够写出像这样的东西是很好和实用的:
class Foo(NamedTuple):
bar: int
baz: str