Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why can we inherit `typing.NamedTuple`?

After Python 3.6, we have typing.NamedTuple, which is a typed version of collections.namedtuple(), we can inherit it like a class:

class Employee(NamedTuple):
    name: str
    id: int

Compared with collections.namedtuple, this syntax is more beautiful, but I still can’t understand its implementation, whether we look at typing.py file, or do some simple tests, we will find that it is a function rather than a class:

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

I understand that it uses some metaclass magic, but I don’t understand what happens when using class MyClass(NamedTuple). For this reason, I have tried to customize a function to inherit:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

Well, this got a result that I can’t understand. When inheriting a user-defined function, it seems that its class was called. What happened behind this?

>Solution :

typing.NamedTuple uses a really esoteric feature, __mro_entries__:

If a base that appears in class definition is not an instance of type, then an __mro_entries__ method is searched on it. If found, it is called with the original bases tuple. This method must return a tuple of classes that will be used instead of this base. The tuple may be empty, in such case the original base is ignored.

Immediately after the NamedTuple function definition, the following code appears:

_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

This sets NamedTuple.__mro_entries__ to a function that tells the class creation system to use an actual class, _NamedTuple, as the base class. (_NamedTuple then uses metaclass features to customize the class creation process further, and the end result is a class that directly inherits from tuple.)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading