
Python NamedTuple gives you a way to create lightweight, immutable objects that behave like tuples but let you access elements by name instead of index. If you have ever worked with regular tuples and found yourself forgetting which index holds which value, Python NamedTuple solves that problem elegantly. It combines the efficiency of tuples with the readability of classes, making your code cleaner and easier to maintain.
Python NamedTuple lives in the collections module as part of the standard library. You can also define namedtuples using the typing module for a more modern, class-based syntax. Both approaches create tuple subclasses where each position has a meaningful name.
The most common way to create a Python NamedTuple is using the namedtuple factory function from the collections module. You pass it a type name and a sequence of field names, and it returns a new class that you can use to create instances.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 7)
print(p)
print(p.x)
print(p.y)
Output
Point(x=3, y=7)
3
7
The namedtuple function created a new class called Point with two fields, x and y. You can access each value by its field name using dot notation, which is much more readable than using numeric indices like a regular tuple.
You can also specify field names as a single space-separated or comma-separated string instead of a list.
from collections import namedtuple
Color = namedtuple('Color', 'red green blue')
c = Color(255, 128, 0)
print(c)
print(c.red)
print(c.green)
print(c.blue)
Output
Color(red=255, green=128, blue=0)
255
128
0
Both formats work exactly the same way. The string format is a convenient shorthand when you have simple field names.
Python 3.6 introduced a class-based syntax for defining namedtuples using the typing module. This approach lets you add type annotations and feels more like writing a regular class.
from typing import NamedTuple
class Employee(NamedTuple):
name: str
department: str
salary: float
emp = Employee('Alice', 'Engineering', 95000.0)
print(emp)
print(emp.name)
print(emp.department)
print(emp.salary)
Output
Employee(name='Alice', department='Engineering', salary=95000.0)
Alice
Engineering
95000.0
The typing.NamedTuple approach is preferred in modern Python code because it provides type hints and reads like a class definition. The resulting namedtuple works identically to one created with collections.namedtuple.
Since a Python NamedTuple is a subclass of tuple, you can access elements both by name and by index. This dual access makes namedtuples versatile and backwards compatible with code that expects regular tuples.
from collections import namedtuple
Book = namedtuple('Book', ['title', 'author', 'year'])
book = Book('The Pragmatic Programmer', 'David Thomas', 1999)
print(book.title)
print(book[0])
print(book.author)
print(book[1])
print(book.year)
print(book[2])
Output
The Pragmatic Programmer
The Pragmatic Programmer
David Thomas
David Thomas
1999
1999
Both book.title and book[0] return the same value. Named access is clearer in most situations, but index access is useful when you need to iterate or unpack values.
Python NamedTuple instances support unpacking just like regular tuples. You can assign each field to a separate variable in a single statement, which is handy when passing namedtuple data to functions or using it in loops.
from collections import namedtuple
Coordinates = namedtuple('Coordinates', ['latitude', 'longitude'])
location = Coordinates(40.7128, -74.0060)
lat, lon = location
print(f'Latitude: {lat}')
print(f'Longitude: {lon}')
print('Iterating over fields:')
for value in location:
print(value)
Output
Latitude: 40.7128
Longitude: -74.006
Iterating over fields:
40.7128
-74.006
Unpacking works because a namedtuple is still a tuple underneath. You get all the tuple behaviors like iteration, slicing, and length checking for free.
A Python NamedTuple is immutable, meaning you cannot change its field values after creation. This is a key property that makes namedtuples safe to use as dictionary keys, set members, and in situations where you need data that should not change.
from collections import namedtuple
Settings = namedtuple('Settings', ['theme', 'font_size'])
config = Settings('dark', 14)
print(config)
try:
config.theme = 'light'
except AttributeError as e:
print(f'Error: {e}')
Output
Settings(theme='dark', font_size=14)
Error: can't set attribute
Attempting to modify a field raises an AttributeError. If you need a modified copy, use the _replace method instead of trying to change the original.
The _replace method creates a new namedtuple instance with some fields changed while keeping the rest unchanged. Since namedtuples are immutable, this is the standard way to create modified versions of your Python NamedTuple objects.
from collections import namedtuple
Product = namedtuple('Product', ['name', 'price', 'stock'])
item = Product('Keyboard', 49.99, 150)
print(f'Original: {item}')
updated_item = item._replace(price=39.99, stock=200)
print(f'Updated: {updated_item}')
print(f'Original unchanged: {item}')
Output
Original: Product(name='Keyboard', price=49.99, stock=150)
Updated: Product(name='Keyboard', price=39.99, stock=200)
Original unchanged: Product(name='Keyboard', price=49.99, stock=150)
The _replace method returns a brand new namedtuple. The original stays exactly as it was, which is the expected behavior for immutable objects.
Python NamedTuple provides several useful built-in methods. The _fields attribute returns a tuple of field names, and _asdict converts the namedtuple into an OrderedDict (or a regular dict in Python 3.8+).
from collections import namedtuple
Student = namedtuple('Student', ['name', 'grade', 'gpa'])
student = Student('Bob', 'A', 3.9)
print(f'Fields: {student._fields}')
print(f'As dict: {student._asdict()}')
student_dict = student._asdict()
print(f'Name from dict: {student_dict["name"]}')
Output
Fields: ('name', 'grade', 'gpa')
As dict: {'name': 'Bob', 'grade': 'A', 'gpa': 3.9}
Name from dict: Bob
The _fields attribute is helpful when you need to inspect the structure of a namedtuple dynamically. The _asdict method is perfect for serializing namedtuples to JSON or passing data to functions that expect dictionaries.
You can set default values for Python NamedTuple fields. With the typing.NamedTuple syntax, defaults work exactly like they do in class definitions. With collections.namedtuple, you use the defaults parameter.
from collections import namedtuple
Connection = namedtuple('Connection', ['host', 'port', 'timeout'], defaults=[3306, 30])
conn1 = Connection('localhost')
conn2 = Connection('db.example.com', 5432)
conn3 = Connection('db.example.com', 5432, 60)
print(conn1)
print(conn2)
print(conn3)
Output
Connection(host='localhost', port=3306, timeout=30)
Connection(host='db.example.com', port=5432, timeout=30)
Connection(host='db.example.com', port=5432, timeout=60)
Defaults are applied from right to left. In this example, port defaults to 3306 and timeout defaults to 30, but host has no default and must always be provided.
Here is the same thing using the typing.NamedTuple syntax.
from typing import NamedTuple
class Connection(NamedTuple):
host: str
port: int = 3306
timeout: int = 30
conn = Connection('localhost')
print(conn)
Output
Connection(host='localhost', port=3306, timeout=30)
The typing syntax makes defaults more explicit since each field shows its type and default value on the same line.
The _make class method creates a Python NamedTuple instance from an existing iterable like a list or a tuple. This is useful when you are reading data from files, databases, or APIs and need to convert raw sequences into structured namedtuples.
from collections import namedtuple
Record = namedtuple('Record', ['id', 'name', 'score'])
data_list = [101, 'Charlie', 88.5]
record = Record._make(data_list)
print(record)
csv_row = '102,Diana,92.3'
values = csv_row.split(',')
record2 = Record._make([int(values[0]), values[1], float(values[2])])
print(record2)
Output
Record(id=101, name='Charlie', score=88.5)
Record(id=102, name='Diana', score=92.3)
The _make method is a clean alternative to unpacking arguments manually. It validates that the iterable has the right number of elements and raises a TypeError if it does not match.
A Python NamedTuple is more readable and self-documenting compared to a regular tuple. Here is a side-by-side comparison to see the difference.
from collections import namedtuple
regular_tuple = ('Alice', 30, 'Engineer')
print(f'Name: {regular_tuple[0]}')
print(f'Age: {regular_tuple[1]}')
print(f'Role: {regular_tuple[2]}')
Person = namedtuple('Person', ['name', 'age', 'role'])
named = Person('Alice', 30, 'Engineer')
print(f'Name: {named.name}')
print(f'Age: {named.age}')
print(f'Role: {named.role}')
print(f'Are they equal? {regular_tuple == named}')
print(f'Type of named: {type(named).__bases__}')
Output
Name: Alice
Age: 30
Role: Engineer
Name: Alice
Age: 30
Role: Engineer
Are they equal? True
Type of named: (<class 'tuple'>,)
The namedtuple version is self-explanatory. You know exactly what each field represents without needing comments. And since namedtuple inherits from tuple, it is fully compatible wherever regular tuples are expected.
This example brings together everything covered about Python NamedTuple. It defines a namedtuple for managing a simple inventory system, demonstrates creation, access, modification with _replace, conversion with _asdict, and building from raw data with _make.
from typing import NamedTuple
class InventoryItem(NamedTuple):
sku: str
name: str
quantity: int
price: float
category: str = 'General'
def display_item(item):
print(f' SKU: {item.sku}')
print(f' Name: {item.name}')
print(f' Quantity: {item.quantity}')
print(f' Price: ${item.price:.2f}')
print(f' Category: {item.category}')
print()
item1 = InventoryItem('WDG-001', 'Wireless Mouse', 45, 29.99, 'Electronics')
item2 = InventoryItem('WDG-002', 'USB Cable', 200, 7.49)
print('--- Inventory Items ---')
print('Item 1:')
display_item(item1)
print('Item 2 (default category):')
display_item(item2)
print(f'Fields: {InventoryItem._fields}')
print()
updated_item1 = item1._replace(quantity=40, price=24.99)
print('After restocking and price change:')
display_item(updated_item1)
item_dict = item1._asdict()
print(f'Item 1 as dictionary: {item_dict}')
print()
raw_data = ['WDG-003', 'Laptop Stand', 30, 54.99, 'Furniture']
item3 = InventoryItem._make(raw_data)
print('Item created from raw data:')
display_item(item3)
inventory = [item1, item2, item3]
total_value = sum(item.quantity * item.price for item in inventory)
print(f'Total inventory value: ${total_value:.2f}')
print('Items with quantity under 50:')
for item in inventory:
if item.quantity < 50:
print(f' {item.name} - {item.quantity} units')
Output
--- Inventory Items ---
Item 1:
SKU: WDG-001
Name: Wireless Mouse
Quantity: 45
Price: $29.99
Category: Electronics
Item 2 (default category):
SKU: WDG-002
Name: USB Cable
Quantity: 200
Price: $7.49
Category: General
Fields: ('sku', 'name', 'quantity', 'price', 'category')
After restocking and price change:
SKU: WDG-001
Name: Wireless Mouse
Quantity: 40
Price: $24.99
Category: Electronics
Item 1 as dictionary: {'sku': 'WDG-001', 'name': 'Wireless Mouse', 'quantity': 45, 'price': 29.99, 'category': 'Electronics'}
Item created from raw data:
SKU: WDG-003
Name: Laptop Stand
Quantity: 30
Price: $54.99
Category: Furniture
Total inventory value: $4497.25
Items with quantity under 50:
Wireless Mouse - 45 units
Laptop Stand - 30 units