How to make an immutable object in Python?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Python does not have a keyword that turns an arbitrary custom class into a perfectly immutable object. Instead, immutability is usually a design choice: restrict attribute mutation, choose immutable field types, and avoid exposing internal mutable state.
For most modern Python code, the best starting point is a frozen dataclass. It is concise, readable, and communicates intent clearly.
Use A Frozen Dataclass
A frozen dataclass prevents attribute reassignment after construction:
Trying to assign p.x = 10 raises an exception. This gives you practical immutability for the object interface.
Immutability Depends On The Fields Too
A frozen outer object is not truly immutable if it contains mutable fields:
You cannot rebind values, but you can still mutate the list itself. A better design uses immutable field types such as tuples or frozensets:
That is the real rule: immutability is only as strong as the mutability of the contained data.
Alternative: Custom __setattr__
Before dataclasses became the default answer, developers often blocked assignment manually:
This works, but it is more code and easier to get wrong than a frozen dataclass.
Why Immutability Helps
Immutable objects are easier to reason about because their state does not drift after creation. They are often safer to share across functions and threads, and they are good candidates for dictionary keys when their fields are also hashable.
The main tradeoff is that updates become replace-instead-of-modify operations. That is usually a good trade in value-object style code.
Named Tuples And Value Objects
Not every immutable object needs a custom class. For lightweight value objects, tuples, NamedTuple, and frozen dataclasses often cover most needs with far less code than a hand-written class hierarchy. Choosing the smallest construct that expresses the data usually makes immutability easier to maintain.
Construction Time Is Your Mutation Window
Immutable-object patterns usually concentrate all allowed mutation into object creation. After __init__ or dataclass construction finishes, the public state should stop changing. Thinking in terms of “build once, then freeze” helps keep the design consistent.
Hashability Often Follows Immutability
Value objects are especially useful when you want instances to behave well as dictionary keys or set members.
That makes immutable instances easier to reuse confidently.
It also reduces accidental mutation bugs in larger codebases.
Common Pitfalls
- Freezing the object while storing mutable fields such as lists or dictionaries inside it.
- Assuming
__slots__alone makes an object immutable. - Writing a custom
__setattr__solution when a frozen dataclass would be clearer. - Exposing internal mutable objects through methods or properties.
- Confusing practical immutability with absolute tamper-proofing in Python.
Summary
- In modern Python, a frozen dataclass is usually the best way to model an immutable object.
- Immutability depends on both the object and the types of its fields.
- Prefer tuples, frozensets, and other immutable containers for stored state.
- Custom
__setattr__patterns still work, but they are more error-prone. - Think of immutability as a design discipline, not just a decorator flag.

