Frozen Dataclasses
Overview
A frozen dataclass is a dataclass where instances cannot be modified after initialization. You create it by passing `frozen=True` to the @dataclass decorator: @dataclass(frozen=True) class Point: x: int y: int This makes the class immutable, similar to a `tuple`. It automatically generates a __hash__ method, allowing instances to be used as dict keys or stored in a set. Mental model: A frozen dataclass is a read-only container; once you lock it, any attempt to change its fields raises a `FrozenInstanceError`. p = Point(1, 2) p.x = 3 # Raises FrozenInstanceError The `frozen=True` parameter only prevents reassignment of the fields. It does not make the object deeply immutable. @dataclass(frozen=True) class Group: members: list[str] g = Group(['Alice']) g.members.append('Bob') # Works! The list itself mutated If a field contains a mutable object like a list or dict, you can still mutate that inner object.
Immutability prevents accidental state changes in data passed around your application. It also makes objects hashable, meaning they can be used as keys in a dict or elements in a set.
Where used: FastAPI settings, Domain-Driven Design (Value Objects), Caching keys
Why learn this
- You can safely pass data objects around without worrying about other functions mutating them.
- You can use complex data structures as keys in a dict or elements in a set.
Code walkthrough
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
points_set = {p1, p2}
print(len(points_set))
Focus: points_set = {p1, p2} shows that frozen dataclasses are hashable and equal instances are deduplicated.
Aha moment
from dataclasses import dataclass
@dataclass(frozen=True)
class CacheKey:
query: str
my_cache = {CacheKey('users'): 42}
print(my_cache[CacheKey('users')])
Prediction: Will this successfully retrieve the value 42, or raise a KeyError because they are different objects?
Common guess: KeyError, because they are two different instances in memory.
It prints 42. The `frozen=True` parameter generates a __hash__ method based on the fields. Since both instances have identical fields, they hash to the same value and __eq__ evaluates to True, allowing them to act as identical dict keys.
Common mistakes
- Mutating internal lists: The `frozen=True` parameter only applies to the attributes directly on the class. If a field is a mutable object like a list, you can still mutate the list itself.
- Manual __hash__ conflicts: Providing a custom __hash__ or __eq__ method can conflict with the one generated by `frozen=True`, leading to unexpected equality behaviors.
Glossary
- immutable
- An object whose state or data cannot be changed or modified after it is created. Example: `frozenset`([1, 2])
- hashable
- An object that has a fixed value that never changes during its lifetime, allowing it to be used as a dict key or set item. Example: hash('text')
Recall questions
- How do you make a dataclass immutable?
- What additional magic method does `frozen=True` automatically generate?
- Why would you want to make a dataclass frozen?
- Does `frozen=True` make the dataclass deeply immutable?
Understanding checks
What is printed when this code is executed?
FrozenInstanceError
Because `frozen=True` prevents reassignment of fields after the object is created. Attempting to assign c.retries = 5 raises a FrozenInstanceError.
What is printed when this code is executed?
2
Frozen dataclasses only prevent reassignment of the attribute, such as `t.members` = [...]. They do not make the underlying mutable objects, like a list, immutable. The list itself can still be modified.
Practice tasks
Secure the Configuration
Given this standard dataclass AppConfig, modify it so that instances cannot be modified after initialization and can be used as dict keys.
Challenge
Using Dataclasses in Sets
Create a frozen dataclass UserIdentifier with two str fields: email and `tenant_id`. Then, initialize a set containing two identical instances of UserIdentifier and print the len of the set.