Class vs Instance Attributes in Python Explained
Python differentiates between class attributes and instance
attributes through where they are defined, how they are stored in
memory, and how its internal lookup mechanism resolves them. Class
attributes belong to the class itself and are shared by all instances,
while instance attributes are distinct to each individual object.
Understanding this distinction relies on understanding Python's
underlying namespace dictionaries (__dict__) and the order
in which Python searches for attributes.
Definition and Namespace Storage
The primary syntactic difference lies in where the attribute is defined:
- Class Attributes: Declared directly within the
class body, outside of any methods. Python places these attributes in
the class’s own namespace dictionary
(
ClassName.__dict__). - Instance Attributes: Typically defined inside
constructor methods like
__init__using theselfkeyword (e.g.,self.name = value). Python places these directly into the specific instance’s namespace dictionary (instance.__dict__).
class Car:
wheels = 4 # Class attribute
def __init__(self, color):
self.color = color # Instance attributeIn this example, Car.__dict__ contains
'wheels': 4, while an instance
my_car = Car("red") has its own dictionary
my_car.__dict__ containing 'color': 'red'.
The Lookup Chain
When you access an attribute via an instance (for example,
my_car.attribute), Python uses a specific resolution
order:
- Instance Namespace: Python first inspects the
instance's
__dict__. If the attribute is present, Python returns its value immediately. - Class Namespace: If the attribute is not found in
the instance, Python inspects the class's
__dict__. If present, it returns the class attribute. - Parent Classes: If not found in the class, Python traverses the method resolution order (MRO) through any base classes.
- Attribute Error: If the attribute is not found
anywhere in the hierarchy, Python raises an
AttributeError.
Because of this order, an instance can read a class attribute
directly (my_car.wheels evaluates to 4) as
long as the instance does not define an attribute with the same
name.
Assignment and Shadowing
Python distinguishes clearly between read operations and write operations on instance references:
- Reading via an instance can fall back to the class attribute.
- Writing via an instance always targets the instance's local namespace.
If you execute my_car.wheels = 6, Python does not update
the class attribute. Instead, it creates a new instance attribute named
wheels inside my_car.__dict__. From that point
forward, my_car.wheels returns 6 because the
instance namespace takes precedence over the class namespace. This
behavior is known as shadowing.
Other instances of Car remain unaffected and still
reference the class attribute wheels = 4. To explicitly
modify a class attribute for all instances, you must assign to it using
the class name directly: Car.wheels = 6.