Python namedtuple: Structured Data Without the Overhead of a Full Class
A regular tuple stores values by position. A namedtuple stores them by position and by name. You get point[0] and point.x — both work. The result is more readable than positional tuples, more memory-efficient than a class with __dict__, and immutable like any tuple.
Creating a namedtuple
from collections import namedtuple
# Factory function: type name, then field names as a string or listPoint = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)print(p.x) # 3 — attribute accessprint(p[0]) # 3 — also works as a regular tupleprint(p) # Point(x=3, y=4) — readable repr
# Unpacking works just like a tuplex, y = pprint(x, y) # 3 4Practical Example: Student Records
from collections import namedtuple
Student = namedtuple('Student', ['name', 'age', 'grade'])
students = [ Student("Alice", 20, "A"), Student("Bob", 22, "B"), Student("Carol", 21, "A"),]
# Access by name — much clearer than students[0][2]for s in students: print(f"{s.name} (age {s.age}): {s.grade}")
# Sort by grade then by namesorted_students = sorted(students, key=lambda s: (s.grade, s.name))print([s.name for s in sorted_students])# ['Alice', 'Carol', 'Bob']The clarity gain over plain tuples becomes obvious as soon as you have more than two fields. student[2] tells you nothing; student.grade is self-documenting.
_asdict() — Convert to an OrderedDict
from collections import namedtuple
Product = namedtuple('Product', ['name', 'price', 'stock'])p = Product("Widget", 9.99, 150)
d = p._asdict()print(d)# {'name': 'Widget', 'price': 9.99, 'stock': 150}
# Useful for serialisationimport jsonprint(json.dumps(d))# {"name": "Widget", "price": 9.99, "stock": 150}_replace() — Create a Modified Copy
Since namedtuples are immutable, you cannot change a field in place. _replace() returns a new instance with the specified fields changed:
from collections import namedtuple
Config = namedtuple('Config', ['host', 'port', 'debug'])production = Config(host="db.example.com", port=5432, debug=False)
# Create a testing variant without changing the originaltesting = production._replace(host="localhost", debug=True)
print(production)# Config(host='db.example.com', port=5432, debug=False)
print(testing)# Config(host='localhost', port=5432, debug=True)Default Values (Python 3.6.1+)
from collections import namedtuple
# Provide defaults for the last N fieldsConnection = namedtuple('Connection', ['host', 'port', 'timeout', 'retries'], defaults=[30, 3]) # timeout=30, retries=3
c1 = Connection("api.example.com", 443) # uses defaultsc2 = Connection("db.local", 5432, 10, 5) # overrides all defaults
print(c1) # Connection(host='api.example.com', port=443, timeout=30, retries=3)print(c2) # Connection(host='db.local', port=5432, timeout=10, retries=5)namedtuple vs dataclass
Python 3.7 introduced @dataclass. The choice between them:
| Feature | namedtuple | dataclass |
|---|---|---|
| Immutability | Yes (tuple) | Optional (frozen=True) |
| Memory | Tuple (efficient) | Object with __dict__ |
| Default values | Yes (3.6.1+) | Yes |
| Mutable fields | No | Yes (default) |
| Inheritance | Limited | Full |
__post_init__ | No | Yes |
Use namedtuple for small, immutable records where memory matters or where you want tuple interoperability (unpacking, indexing). Use dataclass for anything that needs mutability, computed fields, or complex initialisation logic.
from dataclasses import dataclass
@dataclass(frozen=True) # frozen=True makes it immutable like namedtupleclass Point: x: float y: float
def distance_from_origin(self): return (self.x**2 + self.y**2) ** 0.5
p = Point(3, 4)print(p.distance_from_origin()) # 5.0When you need methods on your record, use a dataclass (or a full class). When you just need named fields and immutability, namedtuple is lighter.