Logia3.jpeg

NamedTuples: An easier way

Python's namedtuples are a great way to write more readable code, but, if you've never heard about them, here's a quick summary.

Namedtuples quick explanation

Tuples

Before talking about namedtuples, let's first talk about Tuples. They are one of the simplest Python's data structures, allowing you to store a sequence of items. The main feature of Tuples is that they are immutable (can't be modified once they are created). On the other hand, their main downside is that they can only be accessed from integer indexes, let's take a look at this dead simple example:

>>> person = ('Milton', 'Banana', 174)
>>> type(person)
<type 'tuple'>

# Item accessing
>>> print(person[0], person[1], person[2])
('Milton', 'Male', 174)

# Immutability
>>> person[0] = 'Miguel'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

You can go deeper about tuples in this link.

As you may think, data accessing through indexes might not be the most readable way in your code, also they could be cumbersome because you have to remember what does every index mean, a difficult task when your code gets older and older.

Fortunately, Python has another built-in data structure: Namedtuples

Namedtuples

Trying to cover Tuple's downsides, Namedtuples were born. As its name says, they are the same tuples but with "names", this means that they have all Tuple's features but also it supports item accessing through unique (human-readable) identifiers. Let's rewrite our previous example with a namedtuple:

>>> from collections import namedtuple

>>> Person = namedtuple('Person', ['name', 'age', 'height'])

>>> person = Person('Milton', 25, 174)

>>> person
Person(name='Milton', age=25, height=174)

# Item accessing
>>> print(person.name, person.age, person.height)
Milton 25 174

# Immutability
>>> person.name = 'Milton'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

If you want to know more about namedtuples click here

Namedtuples share all the benefits from Tuples, but also, they have better memory usage than regular classes. I like to think about namedtuples as Dan Bader described it in its good book Python Tricks:

namedtuples are a memory efficient shortcut to defining an immutable class in Python manually

However...

I still think Namedtuples could be a lot better in terms of how you write them, for example, I don't really like the way you define them:

from collections import namedtuple

Person = namedtuple('Person', ['name', 'age', 'height'])

I personally don't like to define a non-PEP-8-compliant variable (Because it doesn't respect the snake_case rule for variables) like Person just to make it look like a class (Which is not mandatory but is just a good practice) and also I don't think it's clear what's happening when you pass 'Person' as a parameter (Which namedtuples use internally for the __repr__ method).

A bigger problem I found with them is when you want to define optional-default values for their attributes. I went through this problem some months ago and this is what I found to handle it:

In Python 3.7

use the defaults parameter:

>>> from collections import namedtuple
>>> Person = namedtuple(
    'Person',
    ['name', 'age', 'height', 'ears', 'eyes'],
    defaults=(2, 2,)
)
>>> Person('Milton', 25, 174)
Person(name='Milton', age=25, height=174, ears=2, eyes=2)

Before Python 3.7

>>> from collections import namedtuple
>>> Person = namedtuple(
    'Person',
    ['name', 'age', 'height', 'ears', 'eyes']
)
>>> Person.__new__.__defaults__ = [2, 2]
>>> Person('Milton', 25, 174)
Person(name='Milton', age=25, height=174, ears=2, eyes=2)
# Caitlyn had an accident :(
>>> Person(name='Caitlyn', age=40, height=150, ears=1)  # Note that you can also use kwargs
Person(name='Caitlyn', age=40, height=150, ears=1, eyes=2)

In both cases, defaults are applied to the rightmost parameter (from right to left), that's why we could have ears and eyes with a default value but also some required parameters like name, age and height.

A total mess, isn't it?

The Easier Way

Fortunately, I discover a better alternative to handle this using Python 3.5+ typing module.

Taking advantage about the fact that types and classes are equivalent and thus, produce equivalent instances, we could inherit from NamedTuple and define our attributes same as in a regular class, this is how it could look like:

from typing import NamedTuple

class Person(NamedTuple):
    # Type hints are optional, you don't have to use them but they are great!
    # I encorage you to learn more about them :)
    name: str
    age: int
    height: int
    ears: int = 2
    eyes: int = 2

>>> milton = Person('Milton', 25, 174)
>>> milton
Person(name='Milton', age=25, height=174, ears=2, eyes=2)
>>> caitlyn = Person(name='Caitlyn', age=25, height=174, ears=1)
>>> caitlyn
Person(name='Caitlyn', age=25, height=174, ears=1, eyes=2)

# Immutability
>>> milton.name = 'Miguel'

If you want to know more about Python's type checking I strongly recommend reading this great article from Real Python

See? you can write namedtuples using the simplicity of a regular class, but still benefit from immutability and memory-efficience from Tuples.

This will definitely make the use of Namedtuples a more enjoyable thing.