What's the difference between namedtuple and NamedTuple?

6

The 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'])

It's exactly the same, or if not, what are the differences between them?

    
asked by anonymous 08.06.2018 / 20:32

2 answers

1

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
    
        
    27.09.2018 / 03:11
    0

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

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

    You can also add docstrings to classes derived from typing.NamedTuple and add methods.

        
    10.07.2018 / 01:31