Coder Perfect

In Python, what are “named tuples”?


When I was reading about the changes in Python 3.1, I came across something… unexpected:

I’d never heard of named tuples before, and I assumed that items could be indexed by numbers or by keys (like in tuples and lists) (like in dicts). I had no idea they could be indexed in both directions.

As a result, these are my inquiries:

Asked by Denilson Sá Maia

Solution #1

Named tuples are lightweight object types that are simple to generate. Object-like variable dereferencing or standard tuple syntax can be used to refer to named tuple instances. Except that they are immutable, they can be used in the same way as struct or other common record types. Although there is a recipe for implementing them in Python 2.4, they were only added in Python 2.6 and Python 3.0.

It is usual to represent a point as a tuple, for example (x, y). As a result, code like this is generated:

pt1 = (1.0, 5.0)
pt2 = (2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)

It gets more readable when using a named tuple:

from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)

Named tuples, on the other hand, are backwards compatible with regular tuples, therefore the following will still work:

Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
# use index referencing
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)
 # use tuple unpacking
x1, y1 = pt1

As a result, if you think object notation will make your code more pythonic and legible, you should use named tuples instead of tuples. I’ve begun to use them to represent very basic value types, especially when sending them as parameters to functions. Without seeing the context of the tuple packing, it makes the functions more readable.

You may also use them to replace standard immutable classes that only contain fields and no functions. Your named tuple types can even be used as base classes:

class Point(namedtuple('Point', 'x y')):

Named tuples’ attributes, like those of tuples, are immutable:

>>> Point = namedtuple('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
AttributeError: can't set attribute

You’ll need a different type if you wish to change the values. For mutable recordtypes, there is a useful recipe that allows you to change the values of attributes.

>>> from rcdtype import *
>>> Point = recordtype('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
>>> print(pt1[0])

However, I’m not aware of any type of “named list” that allows you to add new fields. In this case, you might only wish to consult a dictionary. Named tuples can be converted to dictionaries using pt1._asdict() which returns {‘x’: 1.0, ‘y’: 5.0} and can be operated upon with all the usual dictionary functions.

As previously said, you should consult the documentation for further information on which these examples were based.

Answered by fmark

Solution #2

A named tuple is just that: a tuple.

It does everything a tuple can.

It’s more than a tuple, though.

It’s a subclass of tuple with named fields and a defined length that’s built programmatically to your specifications.

This, for instance, produces a tuple subclass that, aside from having a set length (in this case, three), can be used anywhere a tuple can be used without breaking. Liskov substitutability is the term for this.

We can now utilize a class definition with typing in Python 3.6. To make a namedtuple, use NamedTuple:

from typing import NamedTuple

class ANamedTuple(NamedTuple):
    """a docstring"""
    foo: int
    bar: str
    baz: list

The above is identical to the below, with the exception that the above includes type annotations and a docstring. Python 2+ has the following features:

>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)

This instantiates it:

>>> ant = ANamedTuple(1, 'bar', [])

We can look at it and use its properties:

>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.baz.append('anything')
>>> ant.baz

To comprehend named tuples, you must first comprehend what a tuple is. A tuple is effectively an immutable list (one that cannot be altered in memory).

A regular tuple can be used in the following ways:

>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
>>> student_tuple[1]
>>> student_tuple[2]

Iterable unpacking can be used to expand a tuple:

>>> first, last, grade = student_tuple
>>> first
>>> last
>>> grade

Named tuples are tuples that allow you to retrieve their elements by name rather than index!

You create a namedtuple in the following way:

>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])

A little more readable use of the API is to use a single string with the names separated by spaces:

>>> Student = namedtuple('Student', 'first last grade')

You can perform everything that tuples can do (see above), plus the following:

>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
>>> named_student_tuple.last
>>> named_student_tuple.grade
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')

A commenter asked:

The namedtuple types are essentially classes that may be created with simple shorthand. Treat them as if they were classes. Pickle and other users will be able to find them if they are defined at the module level.

The following is an example of a working example at the global module level:

>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')

And this exemplifies the inability to look up a definition:

>>> def foo():
...     LocalNT = namedtuple('LocalNT', 'foo bar')
...     return LocalNT('foo', 'bar')
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed

When having the semantics of tuple components explicit in your code benefits your code, use them.

If you would otherwise use an object with unchanging data characteristics and no functionality, you can use them instead.

You can also add functionality to them by subclassing them, for example:

class Point(namedtuple('Point', 'x y')):
    """adding functionality to a named tuple"""
        __slots__ = ()
        def hypot(self):
            return (self.x ** 2 + self.y ** 2) ** 0.5
        def __str__(self):
            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

Switching from named tuples to tuples would almost certainly be a step backwards. The upfront design decision is whether the cost of the additional code is worth the better readability provided by the tuple.

Unlike tuples, named tuples do not consume any additional memory.

all the features of a statically sized list or a subclassed list that behaves like a named tuple (and that somehow blocks the list from changing in size.)

An enlarged (and maybe Liskov-substitutable) version of the first:

from collections import Sequence

class MutableTuple(Sequence): 
    """Abstract Base Class for objects that work like mutable
    namedtuples. Subclass and define your named fields with 
    __slots__ and away you go.
    __slots__ = ()
    def __init__(self, *args):
        for slot, arg in zip(self.__slots__, args):
            setattr(self, slot, arg)
    def __repr__(self):
        return type(self).__name__ + repr(tuple(self))
    # more direct __iter__ than Sequence's
    def __iter__(self): 
        for name in self.__slots__:
            yield getattr(self, name)
    # Sequence requires __getitem__ & __len__:
    def __getitem__(self, index):
        return getattr(self, self.__slots__[index])
    def __len__(self):
        return len(self.__slots__)

To use, simply create a subclass and define __slots__:

class Student(MutableTuple):
    __slots__ = 'first', 'last', 'grade' # customize 

>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
>>> last
>>> grade
>>> student[0]
>>> student[2]
>>> len(student)
>>> 'Lisa' in student
>>> 'Bart' in student
>>> student.first = 'Bart'
>>> for i in student: print(i)

Answered by Aaron Hall

Solution #3

namedtuple is a function that creates a tuple class. We can generate tuples that are also callable by name using that class.

import collections

#Create a namedtuple class with names "a" "b" "c"
Row = collections.namedtuple("Row", ["a", "b", "c"])   

row = Row(a=1,b=2,c=3) #Make a namedtuple from the Row class we created

print row    #Prints: Row(a=1, b=2, c=3)
print row.a  #Prints: 1
print row[0] #Prints: 1

row = Row._make([2, 3, 4]) #Make a namedtuple from a list of values

print row   #Prints: Row(a=2, b=3, c=4)

Answered by The Demz

Solution #4

namedtuples are a great feature, they are perfect container for data. When you have to “store” data you would use tuples or dictionaries, like:

user = dict(name="John", age=20)


user = ("John", 20)

Because dicts are mutable and slower than tuples, the dictionary method is overpowering. Tuples, on the other hand, are immutable and lightweight, but they are difficult to understand for a large number of data fields.

namedtuples are an excellent balance between the two approaches because they are easy to read, lightweight, and immutable (plus they are polymorphic!).

Answered by pygabriel

Solution #5

Backward compatibility with code that checks for the version, such as this, is possible using named tuples.

>>> sys.version_info[0:2]
(3, 1)

While using this approach, future code will be more explicit.

>>> sys.version_info.major
>>> sys.version_info.minor

Answered by John La Rooy

Post is based on