For all practical purposes, they are the same thing.
In fact, the typing.NamedTuple
implementation itself internally uses the collections.namedtuple
structure for all tuple logic, adding some fields to the object.
class NamedTuple(metaclass=NamedTupleMeta):
_root = True
def __new__(self, typename, fields=None, **kwargs):
if kwargs and not _PY36:
raise TypeError("Keyword syntax for NamedTuple is only supported in Python 3.6+")
if fields is None:
fields = kwargs.items()
elif kwargs:
raise TypeError("Either list of fields or keywords can be provided to NamedTuple, not both")
return _make_nmtuple(typename, fields)
Code snippet taken from the source file in the official repository , lines 2170 to 2207
Notice that the return of the class constructor is the return of the _make_nmtuple
function. Such a function has been defined lines above:
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, both __annotations__ and _field_types are used to maintain compatibility.
nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types)
try:
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return nm_tpl
The function return will be nm_tpl
, which was set using collections.namedtuple
, adding the __annotations__
and _field_types
fields. However, in the NamedTupleMeta
class, what NamedTuple
uses as metaclass, there is the following excerpt:
nm_tpl = _make_nmtuple(typename, types.items())
defaults = []
defaults_dict = {}
for field_name in types:
if field_name in ns:
default_value = ns[field_name]
defaults.append(default_value)
defaults_dict[field_name] = default_value
elif defaults:
raise TypeError("Non-default namedtuple field {field_name} cannot "
"follow default field(s) {default_names}"
.format(field_name=field_name,
default_names=', '.join(defaults_dict.keys())))
nm_tpl.__new__.__annotations__ = collections.OrderedDict(types)
nm_tpl.__new__.__defaults__ = tuple(defaults)
nm_tpl._field_defaults = defaults_dict
What are metaclasses?
Where it is clear the addition of the three fields on the default structure namedtuple
: __annotations__
, __defaults__
and _field_defaults
.
But what does all this imply for me as a developer?
Using the latest versions of Python, 3.6+, you'd prefer to use the typing.NamedTuple
structure, for its much simpler to write and more readable.
The definition of typing.NamedTuple
is simpler;
The two code snippets below are, in practice, equivalent, but look at the difference in their definitions.
class Employee(typing.NamedTuple):
name: str
id: int
Employee = collections.namedtuple('Employee', ['name', 'id'])
# Python <= 3.5
Employee = collections.namedtuple('Employee', [('name', str), ('id', int)])
In addition, using typing.NamedTuple
you can easily set default values for the fields:
class Employee(typing.NamedTuple):
name: str = 'Guido'
id: int = 1
Compared to collections.namedtuple
:
Employee = collections.namedtuple('Employee', ['name', 'id'])
Employee.__new__.__defaults__ = ('Guido', 1)
It's much easier to add a docstring to typing.NamedTuple
;
To add a docstring to the created object, simply add it to the class you created.
class Employee(typing.NamedTuple):
""" Documentação da classe Employee """
name: str
id: int
While for collections.namedtuple
you need to do the assignment manually.
Employee = collections.namedtuple('Employee', ['name', 'id'])
Employee.__doc__ = "Documentação da classe Employee"
It is not necessary to repeat the class name. For collections.namedtuple
, in the object definition itself, you must enter the name twice, plus one for each customization of the object you make. With typing.NamedTuple
, the name is indicated only once as the class name.
With typing.NamedTuple
you can easily add methods to the object.
class Employee(NamedTuple):
"""Represents an employee."""
name: str
id: int = 3
def __repr__(self) -> str:
return f'<Employee {self.name}, id={self.id}>'
But note, even if you set Employee
to inherit from typing.NamedTuple
, it will not be a subtype of this.
isinstance(Employee('Guido', 1), typing.NamedTuple) # False