What is the difference between namedtuple and Namedtuple?

Asked

Viewed 770 times

7

To module documentation typing states that the two code snippets below are equivalent.

Using typing.NamedTuple:

from typing import NamedTuple

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

Using collections.namedtuple:

from collections import namedtuple

Employee = namedtuple('Employee', ['name', 'id'])

Are exactly the same thing, or if not, what differences between them?

2 answers

2


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.

  1. 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)
    
  2. 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"
    
  3. 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.

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

0

With typing.NamedTuple it is possible to put type in attributes, while with namedtuple no. Another difference is that with typing.NamedTuple it is possible to have default values for some of the attributes.

To store this information, the new class created has 2 extra attributes: _field_types and _field_defaults.

It is also possible to add docstrings to classes derived from typing.NamedTuple and add methods.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.