为什么我们可以继承`typing.NamedTuple`?

4
在 Python 3.6 之后,我们拥有了 typing.NamedTuple ,它是 collections.namedtuple() 的类型化版本,我们可以像继承类一样继承它:
class Employee(NamedTuple):
    name: str
    id: int

相比于collections.namedtuple,这个语法更加优美,但我仍然不理解它的实现。无论我们看typing.py文件还是进行一些简单测试,我们都会发现它是一个函数而不是一个类:

# Python 3.10.6 typing.py
def NamedTuple(typename, fields=None, /, **kwargs):
    """..."""
    if fields is None:
        fields = kwargs.items()
    elif kwargs:
        raise TypeError("Either list of fields or keywords"
                        " can be provided to NamedTuple, not both")
    try:
        module = sys._getframe(1).f_globals.get('__name__', '__main__')
    except (AttributeError, ValueError):
        module = None
    return _make_nmtuple(typename, fields, module=module)

>>> type(NamedTuple)
<class 'function'>

我知道它使用了一些元类魔法,但是我不理解当使用class MyClass(NamedTuple)时会发生什么。因此,我尝试自定义一个函数进行继承:

>>> def func_for_inherit(*args, **kwargs):
...     print(args, kwargs)
...
>>> class Foo(func_for_inherit):
...     foo: str
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() argument 'code' must be code, not str

好的,这个问题得到了一个我无法理解的结果。当继承一个用户定义的函数时,似乎会调用它的类。这背后发生了什么?


@azro 我知道它不是一个类,但我想知道它如何表现得“像”一个类? - Mechanic Pig
FYI,collections.namedtuple也是一个函数。 - RafazZ
@RafazZ:这是可以的,但你不能使用class Whatever(collections.namedtuple):并让它起作用。 - user2357112
哦,我明白你的意思了。说实话,我把collections.namedtuple看作是一个工厂;而我把typing.NamedTuple看作是该工厂生成的类(或其实例)。我知道这在技术上并不准确,但这是我的大脑处理无知的方式 :) - RafazZ
1个回答

8

typing.NamedTuple使用一个极为费解的特性,即__mro_entries__

如果出现在类定义中的基类不是type的实例,则在其中搜索__mro_entries__方法。如果找到,则使用原始基类元组调用它。该方法必须返回一个类的元组,这些类将被用来代替此基类。元组可以为空,在这种情况下,原始基类将被忽略。

NamedTuple函数定义之后,紧接着出现以下代码:

_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})

def _namedtuple_mro_entries(bases):
    if len(bases) > 1:
        raise TypeError("Multiple inheritance with NamedTuple is not supported")
    assert bases[0] is NamedTuple
    return (_NamedTuple,)

NamedTuple.__mro_entries__ = _namedtuple_mro_entries

这将设置NamedTuple.__mro_entries__为一个函数,告诉类创建系统使用一个实际的类_NamedTuple作为基类。(然后_NamedTuple使用元类功能进一步自定义类创建过程,最终结果是一个直接继承自tuple的类。)


好像我只关注函数本身,忘记了看它周围的东西。所有这些魔法似乎与函数本身没有任何关系,而是在于这个__mro_entries__ - Mechanic Pig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接