Python namedtuple (Take the tuple to the next level)
In this tutorial, we will dive deep into namedtuples: what they are, how to create and manipulate them, and when to use them (or not).
Namedtuples are a part of Python’s built-in collections
module, and they provide a convenient way to bundle data under one name.
They are a subclass of Python’s built-in tuple data type, but come with a twist: namedtuples are tuples where each value (or “field”) can also be accessed by a unique, user-defined name.
Namedtuples are a great choice where you need to group related data together in a structured, immutable format.
- 1 Creating Namedtuples
- 2 Accessing Elements
- 3 Modifying a namedtuple (Using _replace() method)
- 4 Converting namedtuple to a dictionary
- 5 Using _fields method
- 6 Using _make
- 7 Iterating Over a Namedtuple
- 8 Sorting Namedtuples
- 9 Namedtuples vs Tuples, Lists, and Dictionaries
- 10 Namedtuples in Classes
- 11 Namedtuples in Functions
- 12 Nesting Namedtuples
- 13 Namedtuple vs. Tuple
- 14 Namedtuple vs. Dataclass
- 15 Practical examples of where to use namedtuples
- 16 Examples of when not to use namedtuples
Creating Namedtuples
Creating namedtuples in Python involving the namedtuple()
factory function for creating tuple subclasses.
The namedtuple requires a typename and a field name as its parameters. The typename will be the name of the new class.
The field names can be provided as an iterable of strings or as a single space/comma-separated string.
from collections import namedtuple # Define a Car namedtuple Car = namedtuple('Car', 'brand model year color') # Instantiate a Car my_car = Car('Tesla', 'Model S', 2023, 'Red') print(my_car)
Output:
Car(brand='Tesla', model='Model S', year=2023, color='Red')
In this code, we import the namedtuple class from the collections module and define a Car namedtuple with the field names ‘brand’, ‘model’, ‘year’, and ‘color’.
We then construct an instance of the Car namedtuple by passing the appropriate values.
Adding default values
From Python 3.7 onwards, the namedtuple function includes a defaults keyword argument to provide default values.
# Define a Car namedtuple with default color and year Car = namedtuple('Car', 'brand model year color', defaults=('Unknown', 'Black')) # Instantiate a Car without providing color and year my_car = Car('Tesla', 'Model S') print(my_car)
Output:
Car(brand='Tesla', model='Model S', year='Unknown', color='Black')
In the example above, we’ve provided default values for the ‘year’ and ‘color’ fields.
When we create a new Car namedtuple without specifying these fields, the defaults are used.
Accessing Elements
Once we have our namedtuple, we can access its elements in a few different ways.
Using index
Although namedtuples improve upon regular tuples by adding field names, we can still access elements using an index, just like a regular tuple.
from collections import namedtuple # Define a Fruit namedtuple Fruit = namedtuple('Fruit', ['name', 'color']) # Create an instance of Fruit apple = Fruit('Apple', 'Red') print(apple[0]) # Access element by index
Output:
Apple
In this example, we access the name of the fruit using the index 0.
Using the field name (using dot notation)
One of the main features of namedtuples is the ability to access elements using field names. This is typically done using dot notation.
print(apple.name)
Output:
Apple
Here, we’re accessing the name of the fruit using its field name.
Using getattr()
Python’s built-in getattr()
function allows us to access the value of an object’s attribute. In the case of namedtuples, we can use it to access field values.
print(getattr(apple, 'color'))
Output:
Red
Modifying a namedtuple (Using _replace() method)
Since namedtuples are immutable, we can’t directly modify them.
But there’s a workaround to this using the _replace()
method, which returns a new instance with the replaced field(s).
from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(10, 20) # Modify the y-coordinate p = p._replace(y=30) print(p)
Output:
Point(x=10, y=30)
In this example, we replace the y-coordinate of the point from 20 to 30.
Converting namedtuple to a dictionary
To convert a namedtuple to a dictionary, use the _asdict()
method. This is useful when you need key-value pairs from a namedtuple.
d = p._asdict() print(d)
Output:
{'x': 10, 'y': 30}
Here, we convert our namedtuple to a dictionary, with the field names as keys and the corresponding values as values.
Using _fields method
The _fields
attribute returns a tuple listing the field names. It can be useful when you need to iterate over all the fields of a namedtuple.
print(Point._fields)
Output:
('x', 'y')
Here, we’re printing the field names of the Point namedtuple.
Using _make
The _make()
method allows us to create a new instance of a namedtuple from an iterable.
data = [100, 200] new_point = Point._make(data) print(new_point)
Output:
Point(x=100, y=200)
We create a new Point namedtuple from a list of values using the _make()
method.
Iterating Over a Namedtuple
You can iterate over a namedtuple just like you would with a regular tuple. Here’s how:
from collections import namedtuple Person = namedtuple('Person', ['name', 'age', 'city']) # Create an instance of the namedtuple person = Person('John Doe', 30, 'New York') # Iterate over the namedtuple for field in person: print(field)
Output:
John Doe 30 New York
In the above code, we define a namedtuple Person
and create an instance person
. We then iterate over person
using a simple for loop, which prints each field in the namedtuple.
Iterate over field names and values
If you want to iterate over both the field names and values, you can convert the namedtuple to a dictionary and use the items()
method:
from collections import namedtuple Person = namedtuple('Person', ['name', 'age', 'city']) p = Person('John Doe', 30, 'New York') for field_name, field_value in p._asdict().items(): print(f"{field_name}: {field_value}")
Output:
name: John Doe age: 30 city: New York
In this code, we use the _asdict()
method to convert the namedtuple to a dictionary, then use the items()
method to get pairs of field names and values.
Using _fields() and getattr()
You can also use the _fields
method in combination with getattr()
to iterate over both field names and their corresponding values:
from collections import namedtuple Person = namedtuple('Person', ['name', 'age', 'city']) p = Person('John Doe', 30, 'New York') for field_name in p._fields: print(f"{field_name}: {getattr(p, field_name)}")
Output:
name: John Doe age: 30 city: New York
Here, the _fields
method provides the field names, and getattr()
is used to fetch the corresponding value for each field from the person
namedtuple.
Sorting Namedtuples
You can sort a list of namedtuples similar to how you sort a list of tuples. The sorting is based on the natural order of the fields.
However, you can customize the sorting using the itemgetter
function from the operator
module.
from collections import namedtuple from operator import itemgetter Fruit = namedtuple('Fruit', ['name', 'color']) # Create a list of fruits fruits = [Fruit('Apple', 'Red'), Fruit('Banana', 'Yellow'), Fruit('Cherry', 'Red')] # Sort the list of fruits by color sorted_fruits = sorted(fruits, key=itemgetter(1)) for fruit in sorted_fruits: print(fruit)
Output:
Fruit(name='Apple', color='Red') Fruit(name='Cherry', color='Red') Fruit(name='Banana', color='Yellow')
In this example, we define a Fruit namedtuple and create a list of Fruit instances. We then sort this list by the ‘color’ field using the itemgetter
function.
Namedtuples vs Tuples, Lists, and Dictionaries
Let’s compare the efficiency of these data types.
Our benchmark will focus on the creation, memory usage, and simple access of these data structures.
Here’s how we might approach it:
- We’ll create a Namedtuple, Tuple, List, and Dictionary, each containing the same elements.
- We’ll measure the time it takes to create each of these structures.
- We’ll measure the memory usage of each of these structures.
- We’ll measure the time it takes to access an element in each of these structures.
import sys import timeit from collections import namedtuple # The size of the data structure SIZE = 1000000 data = [(i, 'data{}'.format(i)) for i in range(SIZE)] # Define the namedtuple type DataNT = namedtuple('DataNT', 'id value') # Conversion factor from seconds to milliseconds SEC_TO_MSEC = 1000 # Create and measure time and memory for tuple start = timeit.default_timer() tuple_data = tuple(data) tuple_time = (timeit.default_timer() - start) * SEC_TO_MSEC tuple_mem = sys.getsizeof(tuple_data) # Create and measure time and memory for namedtuple start = timeit.default_timer() namedtuple_data = tuple(DataNT(*d) for d in data) namedtuple_time = (timeit.default_timer() - start) * SEC_TO_MSEC namedtuple_mem = sys.getsizeof(namedtuple_data) # Create and measure time and memory for list start = timeit.default_timer() list_data = list(data) list_time = (timeit.default_timer() - start) * SEC_TO_MSEC list_mem = sys.getsizeof(list_data) # Create and measure time and memory for dictionary start = timeit.default_timer() dict_data = dict(data) dict_time = (timeit.default_timer() - start) * SEC_TO_MSEC dict_mem = sys.getsizeof(dict_data) # Access and measure time for tuple start = timeit.default_timer() _ = tuple_data[SIZE//2] tuple_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC # Access and measure time for namedtuple start = timeit.default_timer() _ = namedtuple_data[SIZE//2] namedtuple_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC # Access and measure time for list start = timeit.default_timer() _ = list_data[SIZE//2] list_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC # Access and measure time for dictionary start = timeit.default_timer() _ = dict_data[SIZE//2] dict_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC results = { "tuple": {"creation_time_ms": tuple_time, "memory": tuple_mem, "access_time_ms": tuple_access_time}, "namedtuple": {"creation_time_ms": namedtuple_time, "memory": namedtuple_mem, "access_time_ms": namedtuple_access_time}, "list": {"creation_time_ms": list_time, "memory": list_mem, "access_time_ms": list_access_time}, "dict": {"creation_time_ms": dict_time, "memory": dict_mem, "access_time_ms": dict_access_time}, } print(results)
The results of the benchmark test:
Data Structure | Creation Time (ms) | Memory Usage (bytes) | Access Time (ms) |
---|---|---|---|
Tuple | 13.2023 | 8000040 | 0.0022 |
NamedTuple | 1601.5981 | 8000040 | 0.0016 |
List | 12.2202 | 8000056 | 0.0013 |
Dictionary | 74.7433 | 41943136 | 0.0021 |
Lists and namedtuples provide the fastest access to elements. This is because they store elements in a contiguous block of memory.
Namedtuples in Classes
Namedtuples can be utilized within classes to enhance code readability and organization. Let’s create a class that uses a namedtuple.
from collections import namedtuple Person = namedtuple('Person', ['name', 'age']) class Classroom: def __init__(self): self.students = [] def add_student(self, name, age): student = Person(name, age) self.students.append(student) classroom = Classroom() classroom.add_student('John', 16) classroom.add_student('Emma', 17) for student in classroom.students: print(student)
Output:
Person(name='John', age=16) Person(name='Emma', age=17)
In this example, we have a Classroom class that uses the Person namedtuple to store information about students.
The add_student method creates a new Person namedtuple and adds it to the students list.
Namedtuples in Functions
Namedtuples can be returned from functions, which can improve the readability of your code by giving names to the elements of the tuple.
Let’s create a function that returns a namedtuple.
from collections import namedtuple Result = namedtuple('Result', ['success', 'data']) def fetch_data(): data = "sample data" success = True return Result(success, data) result = fetch_data() print(result)
Output:
Result(success=True, data='sample data')
Here, we have a fetch_data function that returns a Result namedtuple, consisting of a boolean indicating whether the data fetch was successful, and the data itself.
Nesting Namedtuples
Namedtuples can also be nested to create more complex data structures. This can increase the readability of your code.
from collections import namedtuple Address = namedtuple('Address', ['street', 'city', 'state', 'zip_code']) Person = namedtuple('Person', ['name', 'age', 'address']) # Create an Address instance home_address = Address('123 Main St', 'Springfield', 'IL', '62701') # Create a Person instance with a nested Address person = Person('John Doe', 30, home_address) print(person)
Output:
Person(name='John Doe', age=30, address=Address(street='123 Main St', city='Springfield', state='IL', zip_code='62701'))
In this example, we define an Address namedtuple and a Person namedtuple that includes an address field.
This allows us to nest an Address instance within a Person instance, creating a more structured and readable representation of the data.
Namedtuple vs. Tuple
Below is a comparison table between namedtuple and tuple in Python.
Aspect | namedtuple | tuple |
---|---|---|
Access method | By field name or index or using getattr() | By index only |
Code readability | High (each value has a name) | Low (need to remember order of values) |
Memory usage | More than tuples (due to metadata) | Less than namedtuples |
Flexibility | Fixed once defined | Can include any values |
Can be used as dict key | Yes | Yes |
Mutability | Immutable | Immutable |
Method support | Has several helpful methods | Has only count() and index() methods |
Namedtuple vs. Dataclass
Below is a comparison table between namedtuple and dataclass in Python.
Aspect | namedtuple | dataclass |
---|---|---|
Introduced in | Python 2.6 | Python 3.7 |
Access method | By field name or index | By field name |
Mutability | Immutable | Both mutable and immutable versions |
Default values | Not supported natively | Supported |
Methods | Few built-in methods | Can define custom methods |
Inheritance | Can be extended using subclasses | Supports standard Python inheritance |
Code readability | High (each value has a name) | High (each value has a name) |
Memory usage | Less than dataclasses | More than namedtuples |
Type hints | Not supported natively | Supported |
Usage | Better for simpler use cases | Better for complex use cases |
Practical examples of where to use namedtuples
Grouping data & records: Namedtuples can be an effective way to group related data together.
For instance, if you have various data related to a car such as the model, make, year, and color, you can group these data in a namedtuple.
from collections import namedtuple Car = namedtuple('Car', ['make', 'model', 'year', 'color']) car = Car('Toyota', 'Camry', 2018, 'Blue') print(car)
Output:
Car(make='Toyota', model='Camry', year=2018, color='Blue')
In this example, a Car namedtuple is used to group data related to a specific car.
Returning multiple values from functions: Functions in Python can return multiple values, and often, these values are returned as a tuple.
Using a namedtuple instead of a regular tuple can provide more context to the returned values, improving readability.
from collections import namedtuple from math import pi CircleMetrics = namedtuple('CircleMetrics', ['area', 'circumference']) def calculate_circle_metrics(radius): area = pi * radius * radius circumference = 2 * pi * radius return CircleMetrics(area, circumference) metrics = calculate_circle_metrics(5) print(metrics)
Output:
CircleMetrics(area=78.53981633974483, circumference=31.41592653589793)
In this example, the function calculate_circle_metrics
calculates the area and circumference of a circle and returns a CircleMetrics namedtuple containing both values.
Examples of when not to use namedtuples
While namedtuples are useful and versatile, there are certain situations when other data structures might be a better fit.
- When data needs to be mutable: Since namedtuples are immutable, they’re not suitable if you need a data structure where the values can be modified. In such cases, dictionaries or lists might be a better fit.
- When you need a more complex data structure: While namedtuples can be nested, if you need a more complex or custom data structure, you might need to define your own class.
For instance, let’s take an example where we want to represent a student where we would need to add subjects dynamically. In this case, a dictionary would be a more suitable choice.
student = { 'name': 'John Doe', 'age': 16, 'subjects': ['English', 'Math', 'Science'] } # Add a new subject student['subjects'].append('History') print(student)
Output:
{'name': 'John Doe', 'age': 16, 'subjects': ['English', 'Math', 'Science', 'History']}
In this example, a student’s subjects are stored in a list inside a dictionary, allowing you to dynamically add or remove subjects.
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.