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'>)
Named tuples are available in Python's standard library collections module under the namedtuple utility. The type accepts as parameters the name of the typename and names of the fields associated with it. The utility will then return a new tuple sub-class which is named with the given typename.
Python's namedtuple() is a factory function available in collections . It allows you to create tuple subclasses with named fields. You can access the values in a given named tuple using the dot notation and the field names, like in obj. attr .
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With