Странный результат MRO при наследовании непосредственно от type.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'>) 


person Wayne Chang    schedule 16.03.2020    source источник


Ответы (2)


Это связано с тем, что typing.NamedTuple не является на самом деле правильным типом. Это это класс. Но его единственная цель состоит в том, чтобы воспользоваться преимуществами магии метаклассов, чтобы предоставить вам удобный и красивый способ определения типов именованных кортежей. А именованные кортежи происходят напрямую от tuple.

Обратите внимание, в отличие от большинства других классов,

from typing import NamedTuple
class Foo(NamedTuple):
    pass

print(isinstance(Foo(), NamedTuple)

печатает False.

Это связано с тем, что в NamedTupleMeta по существу выполняется самоанализ __annotations__ в вашем class, чтобы в конечном итоге использовать его для возврата класса, созданного вызовом 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

И, конечно же, namedtuple по сути просто создает класс, производный от tuple. По сути, любые другие классы, производные от вашего класса named-tuple, в операторе определения класса игнорируются, потому что это разрушает обычный механизм класса. Это может показаться неправильным, во многом уродливым, но практичность важнее чистоты. И приятно и практично иметь возможность писать такие вещи, как:

class Foo(NamedTuple):
    bar: int
    baz: str
person juanpa.arrivillaga    schedule 27.03.2020

typing.NamedTuple не предназначен для поведения обычного базового класса. Посмотрите, как даже самого NamedTuple нет в MRO нового класса, когда вы «наследуете» от NamedTuple. На самом деле он предназначен только для того, чтобы быть интерфейсом к collections.namedtuple фабрике классов, которую mypy может проверять.

Когда вы наследуете от NamedTuple, его метакласс полностью игнорирует любые базовые классы и создает новый класс, делегируя его collections.namedtuple, а затем заполняет методы, свойства и прочее из исходного пространства имен. Новый класс всегда будет наследовать напрямую от tuple.

person user2357112 supports Monica    schedule 27.03.2020