Python - Named Tuple (collections.namedtuple)

Named tuples extend Python's standard tuple by allowing access to elements through named attributes rather than numeric indices. This creates lightweight, immutable objects that consume less memory...

Key Insights

  • Named tuples provide immutable, memory-efficient data structures with named fields, combining the benefits of tuples and classes without the overhead
  • They’re ideal for representing simple data objects, function return values, and CSV/database records where you need attribute access but don’t require mutability
  • Named tuples support all tuple operations while adding dot notation access, making code more readable and self-documenting than regular tuples or dictionaries

Understanding Named Tuples

Named tuples extend Python’s standard tuple by allowing access to elements through named attributes rather than numeric indices. This creates lightweight, immutable objects that consume less memory than regular classes while providing better code clarity than plain tuples.

from collections import namedtuple

# Define a named tuple type
Point = namedtuple('Point', ['x', 'y'])

# Create instances
p1 = Point(10, 20)
p2 = Point(x=15, y=25)

# Access via attribute names
print(p1.x, p1.y)  # 10 20

# Access via index (still works like a tuple)
print(p1[0], p1[1])  # 10 20

# Unpacking
x, y = p1
print(x, y)  # 10 20

The first argument to namedtuple() is the typename, and the second is the field names. Field names can be provided as a list, tuple, or space/comma-separated string.

# All these are equivalent
Person = namedtuple('Person', ['name', 'age', 'city'])
Person = namedtuple('Person', 'name age city')
Person = namedtuple('Person', 'name, age, city')

Memory Efficiency and Performance

Named tuples are significantly more memory-efficient than regular classes because they don’t maintain a __dict__ for each instance. This makes them ideal for applications handling large datasets.

import sys
from collections import namedtuple

# Regular class
class PersonClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# Named tuple
PersonTuple = namedtuple('PersonTuple', ['name', 'age'])

# Compare memory usage
regular = PersonClass("Alice", 30)
named = PersonTuple("Alice", 30)

print(f"Regular class: {sys.getsizeof(regular) + sys.getsizeof(regular.__dict__)} bytes")
print(f"Named tuple: {sys.getsizeof(named)} bytes")

# Output (approximate):
# Regular class: 152 bytes
# Named tuple: 64 bytes

Practical Use Cases

Function Return Values

Named tuples excel at returning multiple values from functions, making the return values self-documenting.

from collections import namedtuple
import math

Coordinates = namedtuple('Coordinates', ['distance', 'angle'])

def calculate_polar(x, y):
    distance = math.sqrt(x**2 + y**2)
    angle = math.atan2(y, x)
    return Coordinates(distance, angle)

result = calculate_polar(3, 4)
print(f"Distance: {result.distance:.2f}")  # Distance: 5.00
print(f"Angle: {result.angle:.2f}")        # Angle: 0.93

CSV and Database Records

Named tuples work seamlessly with CSV files and database queries, providing clean attribute access to row data.

from collections import namedtuple
import csv

# Reading CSV data
Employee = namedtuple('Employee', ['id', 'name', 'department', 'salary'])

def read_employees(filename):
    with open(filename, 'r') as f:
        reader = csv.reader(f)
        next(reader)  # Skip header
        return [Employee(*row) for row in reader]

# Simulated usage
employees = [
    Employee('101', 'Alice', 'Engineering', '95000'),
    Employee('102', 'Bob', 'Sales', '75000'),
]

# Clean, readable filtering
engineers = [emp for emp in employees if emp.department == 'Engineering']
high_earners = [emp for emp in employees if int(emp.salary) > 80000]

for emp in engineers:
    print(f"{emp.name} - ${emp.salary}")

Configuration Objects

Named tuples provide immutable configuration objects that prevent accidental modifications.

from collections import namedtuple

DatabaseConfig = namedtuple('DatabaseConfig', [
    'host', 'port', 'database', 'username', 'timeout'
])

# Set defaults using _replace or default values
def create_db_config(host='localhost', port=5432, database='mydb', 
                     username='admin', timeout=30):
    return DatabaseConfig(host, port, database, username, timeout)

config = create_db_config(host='prod-server', database='production')
print(f"Connecting to {config.host}:{config.port}/{config.database}")

Advanced Features

Default Values

Python 3.7+ offers namedtuple with defaults through the defaults parameter.

from collections import namedtuple

# Defaults apply to rightmost parameters
Request = namedtuple('Request', ['url', 'method', 'timeout'], defaults=['GET', 30])

r1 = Request('https://api.example.com')
print(r1)  # Request(url='https://api.example.com', method='GET', timeout=30)

r2 = Request('https://api.example.com', 'POST', 60)
print(r2)  # Request(url='https://api.example.com', method='POST', timeout=60)

Field Manipulation

Named tuples provide methods for introspection and creating modified copies.

from collections import namedtuple

Person = namedtuple('Person', ['name', 'age', 'city'])
p = Person('Alice', 30, 'NYC')

# Get field names
print(p._fields)  # ('name', 'age', 'city')

# Convert to dictionary
print(p._asdict())  # {'name': 'Alice', 'age': 30, 'city': 'NYC'}

# Create modified copy
p2 = p._replace(age=31)
print(p2)  # Person(name='Alice', age=31, city='NYC')

# Create from iterable
data = ['Bob', 25, 'LA']
p3 = Person._make(data)
print(p3)  # Person(name='Bob', age=25, city='LA')

Subclassing Named Tuples

You can extend named tuples with custom methods while maintaining immutability.

from collections import namedtuple

class Point(namedtuple('Point', ['x', 'y'])):
    __slots__ = ()  # Prevent __dict__ creation
    
    def distance_from_origin(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(p.distance_from_origin())  # 5.0
print(p)  # Point(3, 4)

Named Tuples vs Alternatives

When to Use Named Tuples

from collections import namedtuple
from dataclasses import dataclass

# Use named tuple for: immutable, simple data structures
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
# p.x = 5  # AttributeError: can't set attribute

# Use dataclass for: mutable objects with default values and methods
@dataclass
class MutablePoint:
    x: int
    y: int
    z: int = 0  # Easy defaults
    
    def move(self, dx, dy):
        self.x += dx
        self.y += dy

mp = MutablePoint(1, 2)
mp.move(3, 4)  # Now at (4, 6)

Performance Comparison

from collections import namedtuple
import timeit

# Setup
Point = namedtuple('Point', ['x', 'y'])

# Named tuple creation
nt_time = timeit.timeit('Point(1, 2)', globals=globals(), number=1000000)

# Regular tuple creation
t_time = timeit.timeit('(1, 2)', number=1000000)

# Dict creation
d_time = timeit.timeit('{"x": 1, "y": 2}', number=1000000)

print(f"Named tuple: {nt_time:.4f}s")
print(f"Regular tuple: {t_time:.4f}s")
print(f"Dictionary: {d_time:.4f}s")

Best Practices

Use named tuples when you need immutable records with named fields but don’t require the full capabilities of a class. They’re perfect for data transfer objects, API responses, and anywhere you’d use a dictionary but want better performance and dot notation access.

Avoid named tuples when you need mutability, complex validation, or extensive methods. In those cases, use dataclasses or regular classes. Keep field names valid Python identifiers and avoid names starting with underscores to prevent conflicts with named tuple methods.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.