Weird MRO result when inheriting directly from typing.NamedTuple

久未见 提交于 2020-05-29 04:07:27

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!