问题
I am confused why FooBar.__mro__
doesn't show <class '__main__.Parent'>
like the above two.
I still don't know why after some digging into the CPython source code.
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'>)
回答1:
This is because typing.NamedTuple
is not really a proper type. It is a class. But its singular purpose is to take advantage of meta-class magic to give you a convenient nice way to define named-tuple types. And named-tuples derive from tuple
directly.
Note, unlike most other classes,
from typing import NamedTuple
class Foo(NamedTuple):
pass
print(isinstance(Foo(), NamedTuple)
prints False
.
This is because in NamedTupleMeta essentially introspects __annotations__
in your class to eventually use it to return a class created by a call to collections.namedtuple
:
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
And of course, namedtuple
essentially just creates a class which derives from tuple
. Effectively, any other classes your named-tuple class derives from in the class definition statement are ignored, because this subverts the usual class machinery. It might feel wrong, in a lot of ways it is ugly, but practicality beats purity. And it is nice and practical to be able to write things like:
class Foo(NamedTuple):
bar: int
baz: str
回答2:
typing.NamedTuple
isn't designed to behave like a normal base class. Look at how even NamedTuple
itself isn't in the new class's MRO when you "inherit" from NamedTuple
. It's really only designed to be an interface to the collections.namedtuple
class factory that mypy
can inspect.
When you inherit from NamedTuple
, its metaclass completely ignores any base classes and creates the new class by delegating to collections.namedtuple
, then fills in methods and properties and stuff from the original namespace. The new class will always inherit directly from tuple
.
来源:https://stackoverflow.com/questions/60707607/weird-mro-result-when-inheriting-directly-from-typing-namedtuple