namedtuple vs dataclass
namedtuple creates an immutable, lightweight tuple with named fields, while @dataclass creates an ordinary, mutable class with a generated __init__, __repr__, and __eq__.
Side-by-side comparison
#Inherits from
- collections.namedtuple
tuple- @dataclass
- An ordinary class (
object)
Fields can be changed after creation
- collections.namedtuple
- No
- @dataclass
- Yes (by default),
frozen=Trueblocks it
Indexing and unpacking like a tuple
- collections.namedtuple
- Yes
A dictionary key or a set element
- collections.namedtuple
- Yes
- @dataclass
- Only with
frozen=True
A default value that is a list
- collections.namedtuple
- Through
defaults=[...]for the trailing fields - @dataclass
- Through
field(default_factory=list)
Typical use
- collections.namedtuple
- Lightweight, fixed data: a function result, a CSV row
- @dataclass
- Data that changes, custom methods, validation
Example
#from collections import namedtuple
from dataclasses import dataclass
Point = namedtuple("Point", ["x", "y"])
@dataclass
class Student:
name: str
points: int = 0
point = Point(3, 4)
student = Student("Emma")
student.points += 40
print(point, point[0])
print(student)Definition and usage
#Both constructs eliminate repetitive code for classes that mainly store data: you declare the fields once, and __init__, a readable __repr__, and __eq__ comparison are generated automatically.
They differ in what the resulting object actually is. namedtuple creates a class that inherits from tuple: the object is immutable, you can index it, unpack it, and use it as a dictionary key, but changing a field ends in an AttributeError. @dataclass creates an ordinary class: the object is mutable unless you add frozen=True, itโs not a tuple, so you donโt index it by number, but itโs easy to add it methods, default values through field(default_factory=...), and validation in __post_init__.
Choose namedtuple when the data is simple, fixed, and meant to be lightweight, e.g. the result of a function returning several values, or a row from a CSV file. Choose @dataclass when the class needs to change while the program runs, needs its own methods, or needs default values like a list or a dictionary.
Syntax
#from collections import namedtuple
Name = namedtuple("Name", ["field1", "field2"])
from dataclasses import dataclass
@dataclass
class Name:
field1: Type
field2: TypeMore examples
#from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
point = Point(3, 4)
try:
point.x = 10
except AttributeError:
print("The field can't be changed")
moved = point._replace(x=10)
print(moved)from dataclasses import dataclass, field
@dataclass
class Task:
title: str
tags: list = field(default_factory=list)
def summary(self):
return f"{self.title}, tags: {len(self.tags)}"
task = Task("Learning Python")
task.tags.append("python")
print(task.summary())
print(task)Good practices
#- A namedtuple works well for returning several values from a function: you can unpack the result like a tuple or read it by field names.
- If you need immutability but also methods and the newer type-annotated syntax, consider
@dataclass(frozen=True)ortyping.NamedTuple. - Always create fields with a mutable default value in
@dataclass(a list, a dictionary) throughfield(default_factory=...), otherwise youโll get an error right at the class definition.
Related entries
#Spotted a mistake or missing an example? Write to us.