直接从类型继承时产生奇怪的MRO结果 [英] Weird MRO result when inheriting directly from typing.NamedTuple

查看:97
本文介绍了直接从类型继承时产生奇怪的MRO结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很困惑为什么FooBar.__mro__不像上面两个一样显示<class '__main__.Parent'>.

I am confused why FooBar.__mro__ doesn't show <class '__main__.Parent'> like the above two.

在深入研究CPython源代码后,我仍然不知道为什么.

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'>) 

推荐答案

这是因为typing.NamedTuple不是真正的 类型.它是一个类.但是其唯一目的是利用元类魔术为您提供一种方便的方法来定义命名元组类型.而命名元组直接来自tuple.

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)

打印False.

这是因为在 NamedTupleMeta 本质上是对您的类中的__annotations__进行内省,以最终使用它返回通过调用collections.namedtuple创建的类:

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

当然,namedtuple本质上只是创建一个从tuple派生的类.实际上,在类定义语句中从您的named-tuple类派生的任何其他类都将被忽略,因为这会破坏通常的类机制.它可能会感觉很错误,在很多方面都很难看,但是实用性胜过了纯洁.并且能够写类似这样的东西是很好且实用的:

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

这篇关于直接从类型继承时产生奇怪的MRO结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆