namedtuple for Lightweight Records
`collections.namedtuple` produces a tuple subclass with named fields, giving you the immutability and packing of a tuple plus the readability of a dataclass. It is the right choice for tiny return-record types that should be cheap and hashable. This snippet covers the basic factory, the typed `NamedTuple` form from `typing`, and the `_replace` and `_asdict` helpers for record-style updates and JSON conversion.
1,084 views
11
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
origin = Point(0, 0)
far = Point(3, 4)
print(origin) # Point(x=0, y=0)
print(far.x, far.y) # 3 4
print(far[0], far[1]) # 3 4 (still index-accessible)
print(origin == Point(0, 0)) # True (value equality)namedtuple(name, fields) returns a class. Instances are immutable, hashable, and compare by value, just like regular tuples. Field access works either by name (pt.x) or index (pt[0]), which gives forward-compatibility with code that expected a plain tuple. The repr is informative (Point(x=0, y=0)), which beats a bare tuple in logs. Use it for tiny grouped values that pass through several layers without changing shape: coordinates, RGB triples, single rows of a parsed CSV.
from typing import NamedTuple
class User(NamedTuple):
id: int
name: str
is_admin: bool = False
u = User(1, 'Ada')
print(u) # User(id=1, name='Ada', is_admin=False)
print(u.is_admin) # False
admin = User(2, 'Bo', is_admin=True)
print(admin.is_admin) # Truetyping.NamedTuple is a class-syntax variant that supports type annotations, default values, and methods. It produces the same runtime shape as collections.namedtuple (immutable tuple subclass) but with cleaner declarations and IDE autocomplete on field types. Defaults work like dataclass defaults: trailing fields can be omitted at construction. Reach for this version in modern code with type hints; reach for collections.namedtuple only when you cannot use typing for some reason.
from collections import namedtuple
User = namedtuple('User', ['id', 'name', 'role'])
u = User(1, 'Ada', 'member')
# Immutable update via _replace
promoted = u._replace(role='admin')
print(promoted) # User(id=1, name='Ada', role='admin')
print(u) # User(id=1, name='Ada', role='member') (unchanged)
# Convert to dict for JSON or comparison
import json
print(json.dumps(u._asdict())) # {"id": 1, "name": "Ada", "role": "member"}
# Build from a dict (useful when reading from JSON)
row = {'id': 2, 'name': 'Bo', 'role': 'admin'}
u2 = User(**row)
print(u2) # User(id=2, name='Bo', role='admin')Since named tuples are immutable, the canonical 'update one field' operation is _replace(field=value), which returns a new instance with the change applied. _asdict() gives you a plain dict for serialisation or comparison, and the **row constructor lets you build a tuple from a dict (great for JSON or DB row hydration). The leading underscore is intentional: it avoids clashing with user-defined fields, since any name without an underscore could be a column. These two helpers are the difference between 'tuple I tolerate' and 'record I prefer over a small dataclass'.
