Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

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 list
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x) # 3 — attribute access
print(p[0]) # 3 — also works as a regular tuple
print(p) # Point(x=3, y=4) — readable repr
# Unpacking works just like a tuple
x, y = p
print(x, y) # 3 4

Practical 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 name
sorted_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 serialisation
import json
print(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 original
testing = 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 fields
Connection = namedtuple('Connection', ['host', 'port', 'timeout', 'retries'],
defaults=[30, 3]) # timeout=30, retries=3
c1 = Connection("api.example.com", 443) # uses defaults
c2 = 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:

Featurenamedtupledataclass
ImmutabilityYes (tuple)Optional (frozen=True)
MemoryTuple (efficient)Object with __dict__
Default valuesYes (3.6.1+)Yes
Mutable fieldsNoYes (default)
InheritanceLimitedFull
__post_init__NoYes

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 namedtuple
class 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.0

When you need methods on your record, use a dataclass (or a full class). When you just need named fields and immutability, namedtuple is lighter.