Table of Contents#
- Understanding Class Variables in Python
- Inheritance and Class Variables: The Basics
- The Common Pitfall: Modifying the Parent Affects the Subclass
- Why Does This Happen? The Attribute Lookup Chain
- Mutable vs. Immutable Class Variables: A Deeper Dive
- How to Avoid This: Making Subclass Variables Independent
- Best Practices for Class Variables in Inheritance
- Conclusion
- References
1. Understanding Class Variables in Python#
Before diving into inheritance, let’s clarify what class variables are and how they differ from instance variables.
What Are Class Variables?#
A class variable is a variable defined in a class (outside any method, including __init__). It is shared across all instances of the class and the class itself. Unlike instance variables (which are unique to each instance), class variables belong to the class object, not individual instances.
Example: Class Variable Basics
class Parent:
# Class variable: shared across all instances and subclasses
class_var = "I'm a class variable"
# Access class variable via the class
print(Parent.class_var) # Output: I'm a class variable
# Access via an instance
obj = Parent()
print(obj.class_var) # Output: I'm a class variable
# Modify via the class (affects all instances/subclasses referencing it)
Parent.class_var = "Updated class variable"
print(obj.class_var) # Output: Updated class variableHere, class_var is defined at the class level and is shared by the Parent class and all its instances.
Class Variables vs. Instance Variables#
| Aspect | Class Variable | Instance Variable |
|---|---|---|
| Definition | Defined outside __init__ (class level) | Defined inside __init__ (instance level) |
| Scope | Shared across all instances/subclasses | Unique to each instance |
| Access | Class.variable or instance.variable | instance.variable |
2. Inheritance and Class Variables: The Basics#
In Python, subclasses inherit attributes (including class variables) from their parent classes. By default, if a subclass doesn’t explicitly define a class variable, it will “borrow” the parent’s class variable.
Example: Inheriting Class Variables
class Parent:
class_var = "Parent's class variable"
class Child(Parent):
# No explicit class_var defined; inherits from Parent
pass
# Child inherits class_var from Parent
print(Child.class_var) # Output: Parent's class variableAt first glance, this seems intuitive: Child reuses Parent’s class_var because it hasn’t defined its own. But this inheritance can lead to unexpected behavior when the parent’s class variable is modified.
3. The Common Pitfall: Modifying the Parent Affects the Subclass#
The confusion arises when you modify the parent’s class variable and suddenly notice the subclass’s “version” of the variable has also changed. Let’s demonstrate this with an example:
Example: The Unexpected Side Effect
class Parent:
count = 0 # Class variable
class Child(Parent):
pass # Inherits count from Parent
# Initial state: Child.count is Parent.count
print("Child.count initially:", Child.count) # Output: Child.count initially: 0
# Modify Parent's class variable
Parent.count = 1
# Now check Child.count...
print("Child.count after modifying Parent:", Child.count) # Output: Child.count after modifying Parent: 1Wait a minute! We never touched Child.count, but it changed when we updated Parent.count. Why?
4. Why Does This Happen? The Attribute Lookup Chain#
To understand this, we need to explore how Python resolves attribute access for classes. When you access Child.class_var, Python follows a strict attribute lookup chain:
- First, check if
Childhas its ownclass_vardefined inChild.__dict__(the class’s namespace dictionary). - If not found, Python climbs the inheritance hierarchy and checks the parent class (
Parent) forclass_varinParent.__dict__. - This continues up to
object(the root of all Python classes) until the attribute is found or anAttributeErroris raised.
In the example above, Child never defines its own count variable, so Child.count is just a reference to Parent.count. Thus, modifying Parent.count directly changes the value Child.count points to.
Proof with __dict__
We can verify this by inspecting the __dict__ attribute of Child and Parent:
print("Child.__dict__ before modification:", Child.__dict__)
# Output: Child.__dict__ before modification: {'__module__': '__main__', '__doc__': None}
# Notice: No 'count' key in Child.__dict__
print("Parent.__dict__['count'] before modification:", Parent.__dict__['count'])
# Output: Parent.__dict__['count'] before modification: 0
Parent.count = 1
print("Parent.__dict__['count'] after modification:", Parent.__dict__['count'])
# Output: Parent.__dict__['count'] after modification: 1
# Child still has no 'count' in its __dict__, so it uses Parent's
print("Child.count after modification:", Child.count) # Output: 1Since Child has no count in its own __dict__, it relies on Parent’s count.
5. Mutable Class Variables: An Even Trickier Case#
The problem becomes more subtle with mutable class variables (e.g., lists, dictionaries). Modifying the contents of a mutable class variable (instead of reassigning it) will affect all classes and instances that reference it—even if the subclass later defines its own variable!
Example: Mutable Class Variables
class Parent:
items = [] # Mutable class variable (list)
class Child(Parent):
pass
# Add an item via Parent
Parent.items.append("Parent item")
print("Child.items after Parent append:", Child.items) # Output: Child.items after Parent append: ['Parent item']
# Now, define Child's own items (too late for the existing data!)
Child.items = []
print("Child.items after defining Child's own:", Child.items) # Output: [] (new empty list)
# But Parent's items are unchanged
print("Parent.items now:", Parent.items) # Output: ['Parent item']Here, Child.items initially references Parent.items (a mutable list). Appending to Parent.items modifies the shared list, so Child.items sees the change. Only when Child explicitly defines its own items does it get a separate list.
6. How to Avoid This: Making Subclass Variables Independent#
To prevent the parent class from affecting the subclass, the subclass must explicitly define its own class variable. This breaks the link to the parent’s variable by adding the variable to the subclass’s __dict__.
Solution 1: Explicitly Define the Class Variable in the Subclass#
For immutable class variables (e.g., integers, strings), simply re-declare the variable in the subclass:
class Parent:
count = 0
class Child(Parent):
count = Parent.count # Explicitly define Child's own count (copies initial value)
# Modify Parent's count
Parent.count = 1
# Child's count is now independent
print("Child.count after Parent modification:", Child.count) # Output: 0 (unchanged!)Now, Child.__dict__ contains count, so Child.count no longer references Parent.count.
Solution 2: Copy Mutable Class Variables#
For mutable variables (e.g., lists, dictionaries), use a copy to avoid sharing the same object:
class Parent:
items = [1, 2, 3] # Mutable list
class Child(Parent):
# Create a new list (copy of Parent.items) instead of referencing the same list
items = list(Parent.items) # Shallow copy; use copy.deepcopy() for nested structures
# Modify Parent's items
Parent.items.append(4)
# Child's items remain independent
print("Child.items:", Child.items) # Output: [1, 2, 3] (unchanged)
print("Parent.items:", Parent.items) # Output: [1, 2, 3, 4] (modified)7. Best Practices for Class Variables in Inheritance#
To avoid accidental side effects with class variables and inheritance:
- Explicitly Initialize Subclass Variables: Always define class variables in subclasses if they should be independent of the parent.
- Avoid Mutable Class Variables in Inheritance: Mutable variables (lists, dicts) are prone to unintended sharing. Use immutable types (int, str) or instance variables instead when possible.
- Document Inherited Class Variables: Clearly note if a subclass is intended to inherit and share a parent’s class variable (e.g., for shared configuration).
- Use
@classmethodfor Class-Level Logic: If you need to modify class variables, encapsulate the logic in a class method to avoid direct external modification.
8. Conclusion#
The confusion arises because subclasses inherit class variables from parents by reference, not by value—unless the subclass explicitly defines its own variable. When you modify a parent’s class variable, any subclass that hasn’t overridden it will reflect the change, thanks to Python’s attribute lookup chain.
By explicitly defining class variables in subclasses (and copying mutable objects when needed), you can ensure independence between parent and subclass variables. Understanding this behavior is key to writing predictable, bug-free Python code.