For all practical purposes, they are the same.
Indeed, the very implementation of typing.NamedTuple
uses the structure internally collections.namedtuple
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 source file in the official repository, lines 2170 to 2207
Note that the return of the class constructor is the return of the function _make_nmtuple
. Such a function has been defined above lines:
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 return of the function will be nm_tpl
, which has been defined using collections.namedtuple
, adding the fields __annotations__
and _field_types
. Nevertheless, in the class NamedTupleMeta
, which the NamedTuple
use 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 to add the three fields over the standard structure namedtuple
: __annotations__
, __defaults__
and _field_defaults
.
But what it all entails for me as a developer?
Using the latest versions of Python, 3.6+, prefer to use the structure typing.NamedTuple
, 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 see 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, by 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 one docstring in typing.NamedTuple
;
To add a docstring in the created object, simply add it normally to the class you created.
class Employee(typing.NamedTuple):
""" Documentação da classe Employee """
name: str
id: int
While for collections.namedtuple
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. To collections.namedtuple
, in the object definition itself it will be necessary to enter the name twice, plus one for each customization of the object you do. With typing.NamedTuple
, name is given only once as class name.
With typing.NamedTuple
it is possible to 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 attention, even if you define Employee
as inheriting from typing.NamedTuple
, he will not be a sub-type of this.
isinstance(Employee('Guido', 1), typing.NamedTuple) # False