Medium12/31/2024
Understanding Mutable Default Arguments in Classes
Drona Raj Gyawali
In Programming, mutable default arguments in classes can lead to unexpected bugs and unintended behavior. This happens because mutable objects, such as lists or dictionaries, are shared across all instances of a class when defined as default values at the class level.
1. What are Mutable and Immutable Objects?
- Mutable Objects: These can be changed after they are created.
Examples: list, dict, set - Immutable Objects: These cannot be changed after they are created.
Examples: int, float, str, tuple
2. Mutable Defaults in Classes — The Problem
When you define a class-level attribute with a mutable default value (like a dictionary or list), that default value is shared across all class instances.
🐍 Problematic Example (Mutable Class Attribute)
class Example:
mutable_attr = [] # Mutable class-level attribute (a list)
# Create two instances
obj1 = Example()
obj2 = Example()
# Add something to obj1's list
obj1.mutable_attr.append('hello')
# Check obj2's list
print(obj2.mutable_attr) # Output: ['hello'] <-- Unexpected!
📝 What Happened?
- Both obj1 and obj2 share the same list (mutable_attr) because it's defined at the class level, not the instance level.
- Changing mutable_attr in obj1 also changes it for obj2. This can lead to bugs and unintended behavior.
3. Class-Level vs Instance-Level Attributes
- Class-Level Attribute: Shared across all instances.
- Instance-Level Attribute: Unique to each instance.
🛠️ Solution 1: Use ClassVar
To make it clear that a mutable attribute is intended to be shared across instances, we use ClassVar.
from typing import ClassVar
class Example:
# Explicitly declare it as a class attribute
mutable_attr: ClassVar[list] = []
# Now it's clear to everyone reading the code that `mutable_attr` is a class attribute.
🛠️ Solution 2: Use None for Instance-Level Defaults
If you want every instance to have its own mutable object, use None and initialize it in the constructor.
class Example:
def __init__(self):
self.mutable_attr = [] # Unique list for every instance
# Create two instances
obj1 = Example()
obj2 = Example()
# Add something to obj1's list
obj1.mutable_attr.append('hello')
# Check obj2's list
print(obj2.mutable_attr) # Output: [] <-- Correct!
🧠 Quick Recap:
- Mutable defaults (e.g., [], {}) can cause bugs if shared unintentionally.
- Use ClassVar for shared class-level attributes.
- Use __init__ for instance-level attributes.