Introduction
You already know about Python tuple data type. Tuples are data structures that can hold both homogenous and heterogeneous data. Tuples come under sequence data types meaning the sequence of the data matters and you can access the elements based on their index position. And, most importantly, tuples are immutable objects.
>>> # Tuples holding homogenous data
>>> nums = (10, 20, 3, 40, 50)
>>> # Tuples holding heterogeneous data
>>> person = ('Chetan Ambi', 35, 'India')
>>> # Accessing tuple based on index position
>>> person[0]
'Chetan Ambi'
>>> person[1]
35
>>> # Tuples are immutable
person[0] = 'Swathi Ambi'
The goal of this article is to understand namedtuples that are closely related to tuples. If you have never heard of named tuples before then you are going to learn something new from this article. Let’s dive in.
Namedtuples
As the name suggests, a named tuple is a tuple with a name but it does more than just a tuple. In the case of a tuple, you can access tuple elements with index position whereas a named tuple allows you to access the elements with names. It makes your code more readable and Pythonic.
In the below example, apple and amazon are the two tuple objects. It is obvious from this example that the first element and second element indicate the company name and the founders respectively. But, you may not know what the third element represents. It can be when it was publicly listed or when it was founded, or it can be anything.
>>> apple = ('Apple', 'Steve Jobs, Steve Woznaik', 1977)
>>> amazon = ('Amazon', 'Jeff Bezos', 1994)
As you can see, if the number of elements in the tuple increases readability decreases. This is where named tuples shine. They use names (or dot notation) instead of the index position. Let’s see the syntax and examples below to better understand this.
Syntax
In order to use a named tuple, you need to import it from the collections module.
from collections import namedtuple
namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
Where,
* typename — namedtuple returns tuple named typename
* field_names — the names of the fields (either separated by comma or space)
* rename — invalid field names are automatically replaced with positional names.
* defaults — default values for the field names. It can be None or an iterable.
In this example, we created a named tuple called Point. Using Point as a template (similar to class for creating the object) we can create as many Points as needed.
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p1 = Point(10, 20)
>>> p1.x
10
>>> p1.y
20
>>> p2 = Point(100, 200)
>>> p2
Point(x=100, y=200)
Note that named tuples are backward compatible. It means that you can still access the elements using index positions. Refer below.
>>> p1[0]
10
>>> p1[1]
20
Examples
(1). In the below example, we are creating the namedtuple called company. It contains three fields — name of the company, founders, and the year when it was founded.
>>> from collections import namedtuple
>>> company = namedtuple('company', ['name', 'founders', 'founded'])
>>> c1 = company('Apple', 'Steve Jobs, Steve Woznaik', 1977)
>>> c2 = company('Amazon', 'Jeff Bezos', 1994)
>>> c1
company(name='Apple', founders='Steve Jobs, Steve Woznaik', founded=1977)
>>> c2
company(name='Amazon', founders='Jeff Bezos', founded=1994)
(2) In this example, we are creating the namedtuple called stock. It contains fields — stock symbol, year, month, date, open, high, low, and closing price of the stock.
>>> stock = namedtuple('stock', ['symbol', 'year', 'month', 'day', 'open', 'high', 'low', 'close'])
>>> s1 = stock('AAPL', 2021, 12, 7, 169.08, 171.58, 168.34, 171.18)
>>> s1.symbol
'AAPL'
>>> s2 = stock('AMZN', 2021, 12, 7, 3492.00, 3549.99, 3466.69, 3523.29)
>>> s2.symbol
'AMZN'
Namedtuple methods
Since namedtuple is also a tuple, it supports all the methods tuples support such as min, max, len, count, index, etc. Additionally, namedtuple supports _asdict, _make, _replace, _fields, and _field_defaults methods. Here we will only look these new methods.
_asdict: converts the named tuple to a dictionary and returns it.
>>> from collections import namedtuple
>>> company = namedtuple('company', ['name', 'founders', 'founded'])
>>> c1 = company('Apple', 'Steve Jobs, Steve Woznaik', 1977)
>>> c1._asdict()
{'name': 'Apple', 'founders': 'Steve Jobs, Steve Woznaik', 'founded': 1977}
You can also convert a dictionary to namedtuple using the ** operator. See the below example.
>>> from collections import namedtuple
>>> company = namedtuple('company', ['name', 'founders', 'founded'])
>>> d = {'name': 'Apple', 'founders': 'Steve Jobs, Steve Woznaik', 'founded': 1977}
>>> company(**d)
company(name='Apple', founders='Steve Jobs, Steve Woznaik', founded=1977)
_replace: replaces the specified field by creating a new instance and returns. As you can notice, the memory location is different before and after the replace operation.
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(10, 20)
>>> id(p)
2753050530344
>>> p = p._replace(y=200)
>>> p
Point(x=10, y=200)
>>> id(p)
2753050515640
_make: Creates a new named tuple instance from an existing iterable or sequence.
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(10, 20)
>>> p
Point(x=10, y=20)
>>> p._make(p._make([11, 21]))
Point(x=11, y=21)
>>> p._make(range(2))
Point(x=0, y=2)
_fields: returns the names of the fields.
>>> from collections import namedtuple
>>> stock = namedtuple('stock', ['symbol', 'year', 'month', 'day', 'open', 'high', 'low', 'close'])
>>> s1 = stock('AAPL', 2021, 12, 7, 169.08, 171.58, 168.34, 171.18)
>>> stock._fields
('symbol', 'year', 'month', 'day', 'open', 'high', 'low', 'close')
>>> s1._fields
('symbol', 'year', 'month', 'day', 'open', 'high', 'low', 'close')
>>> p._make(p._make([11, 21]))
Point(x=11, y=21)
>>> p._make(range(2))
Point(x=0, y=2)
_field_defaults: Returns the default values as a dictionary.
Why should you use named tuples?
(1). You should consider using named tuples if it makes your code Pythonic and increases the readability of the code. However, note that there is no difference in the size of the memory between tuples vs. named tuples.
>>> from collections import namedtuple
>>> company = namedtuple('company', ['name', 'founders', 'founded'])
>>> c = company('Apple', 'Steve Jobs, Steve Woznaik', 1977)
Option 1: Using index position to access the element
>>> if c[2] > 2000:
... print('Company founded after 2000')
Option 2: Using field names to access the elements.
>>> if c.founded > 2000:
... print('Company founded after 2000')
As you can see, option 2 makes more sense as it is more readable and Pythonic than option 1.
(2). You could also consider using dictionaries instead of named tuples. Since dictionaries are mutable they are slower than tuples. Hence named tuples are recommended over dictionaries.