Python Class Attributes: How They Work and Common Mistakes
python class attributes: Understand how Python class attributes behave, how they differ from instance attributes, and where they commonly cause bugs in real code.
python class attributes requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In Python, a class attribute is a variable defined directly in the class body. It is shared by every instance of that class unless an instance assigns its own value to the same name. This sharing behavior is a frequent source of bugs, especially when the attribute is mutable. Understanding how class attributes work under the hood is essential for writing predictable object-oriented code.
class Counter: count = 0 c1 = Counter() c2 = Counter() print(c1.count) # 0 print(c2.count) # 0
Both c1 and c2 see the same count value because neither instance has its own count attribute. The attribute exists on the class, and both instances reference it through their class.
Class Attributes vs Instance Attributes
The distinction is straightforward: a class attribute belongs to the class object itself, while an instance attribute belongs to a specific instance. You define a class attribute inside the class body, outside any method. You create an instance attribute by assigning to self inside a method or by assigning directly on an instance.
class User: role = "guest" # class attribute def __init__(self, name): self.name = name # instance attribute u1 = User("alice") u2 = User("bob") print(u1.role) # guest print(u2.role) # guest print(u1.name) # alice print(u2.name) # bob
role is the same object for all instances. name is unique per instance. This difference matters when you mutate the attribute.
How Attribute Lookup Works
Python resolves attribute access on an instance in a defined order. First, it checks the instance's __dict__, which holds instance-specific attributes. If the name is not found there, it checks the class's __dict__, then the base classes in method resolution order. This lookup order explains why assigning an instance attribute with the same name shadows the class attribute.
class Config: timeout = 30 c = Config() print(c.timeout) # 30, from class c.timeout = 60 # creates an instance attribute print(c.timeout) # 60, from instance print(Config.timeout) # 30, class attribute unchanged
The assignment c.timeout = 60 does not modify the class attribute. It creates a new entry in c.__dict__. From that point on, c.timeout returns the instance value, while other instances still see the class value.
The Mutable Default Trap
A class attribute that is a mutable object, such as a list or dictionary, is shared by all instances. If you modify the object in place, every instance sees the change. This is often unintentional.
class ShoppingCart: items = [] # shared mutable class attribute cart1 = ShoppingCart() cart2 = ShoppingCart() cart1.items.append("apple") print(cart2.items) # ['apple']
Both carts share the same list. To give each cart its own list, you must assign the list in __init__ as an instance attribute.
class ShoppingCart: def __init__(self): self.items = [] cart1 = ShoppingCart() cart2 = ShoppingCart() cart1.items.append("apple") print(cart2.items) # []
The same principle applies to any mutable default value. This is why mutable class attributes are usually a design mistake unless the sharing is intentional.
Overriding Class Attributes on Instances
You can override a class attribute per instance by assigning to the same name on the instance. This is a common pattern for customizing behavior without affecting the class or other instances.
class Animal: sound = "generic" dog = Animal() dog.sound = "bark" print(dog.sound) # bark print(Animal.sound) # generic
The override only affects dog. Other instances still see the class value. This pattern is useful when you need a default value but want to allow per-instance customization.
Class Attributes and Inheritance
Class attributes are inherited by subclasses. If a subclass defines an attribute with the same name, it overrides the parent's attribute for that subclass and its instances. This allows you to define defaults in a base class and refine them in derived classes.
class Vehicle: wheels = 4 class Motorcycle(Vehicle): wheels = 2 print(Vehicle.wheels) # 4 print(Motorcycle.wheels) # 2
Instances of Motorcycle see 2, while instances of Vehicle see 4. The lookup order respects the subclass's own class attribute before falling back to the base class.
When to Use Class Attributes
Class attributes are appropriate for values that are genuinely shared across all instances, such as constants, configuration defaults, or counters that track the total number of instances. They also save memory because the value is stored once on the class, not duplicated per instance.
class ConnectionPool: max_connections = 10 active_connections = 0 def __init__(self): ConnectionPool.active_connections += 1
Here max_connections is a constant, and active_connections tracks a global count across all instances. This works because the attribute is shared and the increment is intentional.
Avoid class attributes for values that should be unique to each instance, especially if they are mutable. For per-instance state, always assign in __init__ using self.
Performance and Memory Considerations
Class attributes are stored once on the class object, so they use less memory than instance attributes when many instances exist. However, attribute lookup on an instance first checks the instance dictionary, then the class. This adds a negligible overhead compared to direct instance access, but it is rarely a bottleneck.
The more significant performance concern is unintended sharing of mutable class attributes. If multiple threads or concurrent tasks modify the same mutable object, you can get race conditions or corrupted state. Even in single-threaded code, the shared state can lead to surprising behavior that is difficult to debug.
For values that are truly global, class attributes are efficient. For anything that might vary per instance, using instance attributes avoids the cost of accidental coupling and makes the object's state explicit.