namedtuple & NamedTuple
Reviewed & published by Brayan K
A namedtuple is an immutable, tuple-based record whose positions have readable names, so you can write point.x instead of the cryptic point[0].
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They are the fastest way to replace a confusing tuple like (40.7, -74.0) with a self-documenting record you can pass around, unpack, and use as a dictionary key.
What You'll Learn in This Lesson
- Creating a record with collections.namedtuple
- Accessing fields by name and by index
- Why namedtuples are immutable and what that buys you
- Copying with _replace and inspecting with _asdict
- The typing.NamedTuple class form with defaults
- Choosing between namedtuple and dataclass
1 Creating a Record with collections.namedtuple
namedtuple is a factory: you give it a type name and a list of field names, and it returns a new class. Instances behave like tuples but also expose each position by name:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p) # Point(x=3, y=4)
print(p.x, p.y) # 3 4 (access by name)
print(p[0], p[1]) # 3 4 (still works by index)
# It IS a tuple, so unpacking and tuple tricks work:
x, y = p
print(x + y) # 7
print(len(p)) # 2Because a namedtuple is a real tuple, anything that accepts a tuple accepts it — including being a dictionary key or a set element. You gain readable names for free.
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
c = Color(255, 128, 0)
print(c) # Color(red=255, green=128, blue=0)
print(c.red, c.green, c.blue) # 255 128 0
print(c[0]) # 255
# Unpacks like any tuple
r, g, b = c
print("sum:", r + g + b) # sum: 383
# ✅ Expected output:
# Color(red=255, green=128, blue=0)
# 255 128 0
# 255
# sum: 3832 Immutability and the Helper Methods
You cannot reassign a field — that is the point. To "change" one, you create a fresh copy with _replace. The underscore-prefixed methods are public API (the underscore just avoids clashing with your field names):
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
# p.x = 10 -> AttributeError: can't set attribute
# _replace returns a NEW namedtuple; the original is untouched
moved = p._replace(x=10)
print(moved) # Point(x=10, y=4)
print(p) # Point(x=3, y=4) (unchanged)
# _asdict turns it into a plain dict
print(p._asdict()) # {'x': 3, 'y': 4}
# _fields lists the field names
print(Point._fields) # ('x', 'y')from collections import namedtuple
Account = namedtuple("Account", ["name", "balance"])
acct = Account("Ada", 100)
# Deposit by building a new record (immutability!)
acct = acct._replace(balance=acct.balance + 50)
print(acct) # Account(name='Ada', balance=150)
# Inspect the record
print(acct._asdict()) # {'name': 'Ada', 'balance': 150}
print(Account._fields) # ('name', 'balance')
# ✅ Expected output:
# Account(name='Ada', balance=150)
# {'name': 'Ada', 'balance': 150}
# ('name', 'balance')3 The typing.NamedTuple Class Form (with defaults & methods)
The class-based form reads like a normal class, adds type annotations, supports default values, and even lets you define methods. It is the modern, recommended way:
from typing import NamedTuple
class Employee(NamedTuple):
name: str
department: str = "General" # default value
salary: int = 0 # default value
def raise_pay(self, amount): # methods are allowed!
return self._replace(salary=self.salary + amount)
e = Employee("Ada")
print(e) # Employee(name='Ada', department='General', salary=0)
e = Employee("Ben", "Engineering", 90000)
print(e.department) # Engineering
# Use the method to get a raised copy
e = e.raise_pay(5000)
print(e.salary) # 95000It is still a real tuple under the hood, so _replace, _asdict, _fields, indexing, and unpacking all work exactly as before — you just get cleaner syntax, defaults, and type hints on top.
from typing import NamedTuple
class Book(NamedTuple):
title: str
pages: int
in_stock: bool = True # default value
def label(self):
status = "available" if self.in_stock else "sold out"
return f"{self.title} ({self.pages}p) - {status}"
b = Book("Python 101", 320)
print(b.label()) # Python 101 (320p) - available
b = b._replace(in_stock=False)
print(b.label()) # Python 101 (320p) - sold out
print(Book._fields) # ('title', 'pages', 'in_stock')
# ✅ Expected output:
# Python 101 (320p) - available
# Python 101 (320p) - sold out
# ('title', 'pages', 'in_stock')🎯 Your Turn: Fill in the Blanks
Complete the namedtuple so it builds a record and produces a changed copy. Replace each ___ and match the expected output.
# 🎯 YOUR TURN: complete the namedtuple
from collections import namedtuple
# 1. Create a type named "City" with fields name and population:
City = namedtuple("City", ["name", "___"])
paris = City("Paris", 2_100_000)
print(paris.name) # Paris
print(paris.population) # 2100000
# 2. Build a copy with an updated population using the helper method:
paris = paris.___(population=2_200_000)
print(paris.population) # 2200000
# Expected output:
# Paris
# 2100000
# 2200000Field name blank: population.
Method blank: _replace — it returns a new copy with the updated field.
Common Errors to Avoid
❌ Trying to mutate a field
p = Point(3, 4)
p.x = 10 # AttributeError: can't set attribute✅ Build a new copy: p = p._replace(x=10).
❌ Ignoring the result of _replace
p._replace(x=10) # returns a new tuple that you threw away
print(p.x) # still 3!✅ Assign it back: p = p._replace(x=10).
❌ Putting a default field before a required one (class form)
class Bad(NamedTuple):
pages: int = 0
title: str # error: non-default after default✅ List required fields first, defaulted fields last.
🎯 Mini Challenge: Distance Between Points
Use a NamedTuple to model 2D points, then compute the distance between two of them and shift one along the x-axis.
from typing import NamedTuple
import math
class Point(NamedTuple):
x: float
y: float
def distance_to(self, other):
return math.hypot(self.x - other.x, self.y - other.y)
a = Point(0, 0)
b = Point(3, 4)
print(a.distance_to(b)) # 5.0
# Shift b 6 units along x by making a new copy
b = b._replace(x=b.x + 6)
print(b) # Point(x=9, y=4)
print(round(a.distance_to(b), 4)) # 9.8489
# ✅ Expected output:
# 5.0
# Point(x=9, y=4)
# 9.8489📚 Keep Learning
Go deeper with the official Python documentation:
- collections.namedtuple — factory function reference
- typing.NamedTuple — the class-based form
- dataclasses — the mutable alternative
Frequently Asked Questions
Lesson complete — your tuples have names now!
You can build records with collections.namedtuple, access fields by name and index, copy immutably with _replace, inspect with _asdict and _fields, and use the cleaner typing.NamedTuple class form with defaults and methods.
Practice quiz
What is a namedtuple?
- A mutable dictionary
- A list with a name
- A tuple subclass whose positions also have names
- A frozen set
Answer: A tuple subclass whose positions also have names. A namedtuple is a tuple subclass that adds readable names to its positions.
Given Point = namedtuple('Point', ['x', 'y']) and p = Point(3, 4), what does p.x equal?
- 3
- 4
- (3, 4)
- None
Answer: 3. p.x accesses the first field, which is 3.
Can you still access a namedtuple by index, e.g. p[1]?
- No, only by name
- Only with _get
- Only if frozen
- Yes, it behaves like a tuple
Answer: Yes, it behaves like a tuple. A namedtuple is a real tuple, so indexing like p[1] still works.
What happens when you try p.x = 10 on a namedtuple?
- It updates x
- It raises an AttributeError
- It returns a copy
- It returns None
Answer: It raises an AttributeError. Namedtuples are immutable, so assigning to a field raises AttributeError.
What does p._replace(x=10) do?
- Returns a NEW namedtuple with x=10, leaving p unchanged
- Mutates p in place
- Deletes x
- Raises an error
Answer: Returns a NEW namedtuple with x=10, leaving p unchanged. _replace returns a fresh copy with the field changed; the original stays the same.
What does p._asdict() return for Point(3, 4)?
- (3, 4)
- [3, 4]
- {'x': 3, 'y': 4}
- 'x=3, y=4'
Answer: {'x': 3, 'y': 4}. _asdict() converts the namedtuple into a plain dict of its fields.
What does Point._fields return?
- (3, 4)
- ('x', 'y')
- ['x', 'y']
- {'x', 'y'}
Answer: ('x', 'y'). _fields is a tuple of the field names: ('x', 'y').
In the typing.NamedTuple class form, where must fields WITH defaults go?
- First
- Anywhere
- Only in __init__
- After fields without defaults
Answer: After fields without defaults. Like default function arguments, defaulted fields must come after non-default ones.
A key advantage of typing.NamedTuple over collections.namedtuple is that it...
- Is mutable
- Supports type annotations, defaults, and methods in clean class syntax
- Runs faster
- Has no field names
Answer: Supports type annotations, defaults, and methods in clean class syntax. The class form adds type hints, default values, and methods with readable syntax.
When should you prefer a dataclass over a namedtuple?
- When you need a tuple-like immutable record
- When you want dict keys
- When you need mutability, many methods, or inheritance
- When you want unpacking
Answer: When you need mutability, many methods, or inheritance. Namedtuples are light and immutable; dataclasses suit mutability and richer behavior.
Continue this course
- Previous: Enums (the enum module)
- Next: Randomness with the random Module — randint, choice, shuffle and seeded reproducibility (and when to use secrets)
- Quick reference: Python cheat sheet
Frequently asked questions
What is a namedtuple?
A namedtuple is a tuple subclass whose positions also have names. Instead of point[0] and point[1] you write point.x and point.y, while still getting all the speed, immutability, and unpacking of a regular tuple. It is the lightest possible way to create a small record type with readable field names.
What is the difference between collections.namedtuple and typing.NamedTuple?
They produce the same kind of object, but the syntax differs. collections.namedtuple is a factory function: Point = namedtuple('Point', ['x', 'y']). typing.NamedTuple is a class-based form with type annotations: you write class Point(NamedTuple): x: int; y: int. The class form is more readable, supports default values and methods cleanly, and gives editors type information, so it is usually the better choice in modern code.
Are namedtuples really immutable?
Yes. Like any tuple, you cannot reassign a field — point.x = 5 raises an AttributeError. To get a changed copy you call point._replace(x=5), which returns a brand-new namedtuple and leaves the original untouched. This immutability makes namedtuples safe to use as dictionary keys and to share without fear of accidental mutation.
Should I use a namedtuple or a dataclass?
Use a namedtuple when you want a small, immutable, tuple-like record that can be unpacked and indexed, and where being a tuple is useful (keys, coordinates, return values). Use a dataclass when you need mutability, many methods, inheritance, or richer behavior. Namedtuples are lighter and tuple-compatible; dataclasses are more flexible and feel more like full objects.