-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
130: Dataclasses: Defaults, Ordering, and Frozen Instances
A few years ago, I was reviewing a PR for a colleague who was building a high-frequency trading pipeline. He had a TradeConfig dataclass that held things like API keys and timeout settings. The bug was subtle: one of the validation functions was "cleaning" the config object by updating a timeout value if it seemed too high. Because the object was mutable, that change propagated everywhere. Suddenly, the entire system was timing out prematurely, and we spent an entire afternoon hunting for the one line of code that was mutating a shared configuration object. If that class had been frozen, the code would have crashed immediately at the point of mutation, telling us exactly where the mistake was.
The Danger of Mutable Defaults
Before we get to freezing instances, we need to talk about defaults. You've probably noticed that giving a field a simple default value like name: str = "Unknown" works perfectly. But the moment you try to use a list or a dictionary, you're walking into a trap. If you write tags: list = [], Python creates that list once when the class is defined, not every time you create a new instance. Every single object you instantiate will share the exact same list in memory.
To fix this, we use field(default_factory=...). This tells Python to call a specific function (like list or dict) every time a new instance is created. Here is how that looks in practice:
from dataclasses import dataclass, field
@dataclass
class UserProfile:
username: str
# This ensures every user gets their own unique list of roles
roles: list[str] = field(default_factory=list)
bio: str = "No bio provided"
user1 = UserProfile("alice")
user2 = UserProfile("bob")
user1.roles.append("admin")
print(user2.roles) # Output: [] (Correct!)
Locking Down Data with Frozen Instances
As I mentioned in my trading bot story, there are times when you want to guarantee that an object cannot be changed after it is created. By setting frozen=True in the dataclass decorator, Python generates a class that prevents attribute assignment. If you try to change a value, you'll get a FrozenInstanceError.
Beyond just preventing bugs, making a dataclass frozen has a huge technical advantage: it makes the object hashable. This means you can use a frozen dataclass instance as a key in a dictionary or store it in a set, which is impossible with a standard mutable dataclass.
@dataclass(frozen=True)
class Coordinate:
x: int
y: int
point = Coordinate(10, 20)
# point.x = 15 # This would raise a FrozenInstanceError
# Because it's frozen, we can use it in a set
locations = {Coordinate(0, 0), Coordinate(10, 20)}
Automatic Ordering and Comparison
By default, dataclasses only implement __eq__, meaning two objects are equal if all their fields match. But what if you need to sort a list of these objects? You could write your own __lt__ (less than) method, but that's a lot of boilerplate. Instead, you can use order=True.
When order=True is set, Python automatically generates the comparison methods (<, >, <=, >=). It does this by comparing the fields as a tuple in the order they are defined in the class. If the first fields are equal, it moves to the second, and so on. This is incredibly powerful for things like priority queues or sorted registries.
@dataclass(order=True)
class Task:
priority: int
name: str
# Tasks are sorted primarily by priority, then by name
todo_list = [
Task(2, "Clean room"),
Task(1, "Buy groceries"),
Task(2, "Answer emails"),
]
print(sorted(todo_list))
# Output: [Task(priority=1, name='Buy groceries'), Task(priority=2, name='Answer emails'), Task(priority=2, name='Clean room')]
📋 Practical Task
Exercise: Building an Immutable Game Item Registry
You are building a system for an RPG where items have a rarity level, a power score, and a list of magical properties. You need to ensure that once an item is created, its stats cannot be accidentally changed by the game engine, and you need to be able to sort items by their rarity and power.
Requirements:
- Create a dataclass named
GameItem. - The class must be frozen to prevent mutations.
- The class must support ordering.
- Fields should be:
rarity(int): Lower numbers represent higher rarity (e.g., 1 is Legendary, 5 is Common).power(int): The strength of the item.name(str): The name of the item.properties(list): A list of strings describing magical effects. This must use adefault_factoryto ensure each item has its own list.
Testing your code:
Create three items with different rarities and powers. Put them in a list and use sorted() to verify that they are ordered correctly (primarily by rarity, then by power). Try to change the name of one item to verify that it raises a FrozenInstanceError.
There are no comments for now.