Instance variables vs class variables
Overview
Class variables are shared across all instances of a class, whereas instance variables are unique to each specific object. Think of a class variable as a shared whiteboard in a conference room, while instance variables are each employee's personal notebook. When Python looks up an attribute (e.g., `self.name`), it checks the instance's dictionary first. If the attribute is not found, it falls back to the shared class dictionary. Assigning to `self.var_name` always creates or updates an instance variable, even if a class variable with the same name exists. This creates a sharp edge called shadowing, where an instance quietly ignores the shared class state. class Config: timeout = 10 c = Config() c.timeout = 20 # Shadows the class variable print(Config.timeout) # Still 10 This behavior is a common source of bugs when attempting to update shared counters or configuration. The interaction between class state and instances is covered fully in method-types.
It allows you to store state that applies to the entire group without duplicating it in every object. This saves memory and keeps shared state centralized. Common use cases include: - Caching a shared database connection pool - Defining class-level constants - Maintaining a shared counter across all instances
Where used: Django Model fields, SQLAlchemy declarative base, Pydantic schema definitions
Why learn this
- Django ORM models use class variables to define database columns
- Pydantic models use class variables with type hints for schema definition
- Avoiding accidental shared state bugs across instances
Code walkthrough
class Employee:
company = 'Acme Corp'
def __init__(self, name):
self.name = name
e1 = Employee('Alice')
e2 = Employee('Bob')
print(e1.company)
print(e2.company)
Employee.company = 'GlobalTech'
print(e1.company)
Focus: `Employee.company = 'GlobalTech'` changes the class variable, affecting the output of `e1.company` since `e1` falls back to the class.
Aha moment
class Server:
logs = []
def __init__(self, name):
self.name = name
s1 = Server('web')
s2 = Server('db')
s1.logs.append('started')
print(s2.logs)
Prediction: What does this print?
Common guess: []
Because `logs` is a class variable, `s1.logs.append()` mutates the single shared list. To give each server its own logs, `self.logs = []` must be assigned in `__init__`.
Common mistakes
- Mutating shared class variables: Modifying a mutable class variable (like a `list` or `dict`) through an instance changes it for all instances because they all point to the exact same object in memory. Fix: Initialize mutable variables inside `__init__` as instance variables.
- Shadowing class variables: Assigning to an attribute via `self.var = value` creates an instance variable, hiding the class variable of the same name for that specific instance. It does not update the shared class variable.
Glossary
- connection pool
- A cache of active database connections that can be reused, saving the time and effort of creating a new connection every time. Example: `engine = create_engine(url, pool_size=5)` creates a pool of 5 reusable connections.
- schema definition
- A structured plan or blueprint that describes how data should be organized and what types of data are allowed. Example: `class User(BaseModel): name: str; age: int` is a schema definition in Pydantic.
Recall questions
- Where does Python look first when you try to read an attribute from an object?
- What is the difference in how instance and class variables are shared?
- What happens when you assign a value to `self.var_name` if `var_name` is already defined as a class variable?
Understanding checks
What will this code print?
['admin']
Because `roles` is a class variable and a mutable `list`, it is shared across all instances. Modifying it via one instance affects the shared `list` seen by all other instances.
If you have a class variable `counter = 0` and you execute `self.counter = 5` inside an instance method, what happens?
It creates a new instance variable named `counter` for that specific instance, shadowing the class variable. The class variable remains `0`.
Assigning directly to `self.attribute` always writes to the instance's dictionary. It does not update the shared class dictionary unless you explicitly use the class name, like `MyClass.counter = 5`.
Practice tasks
Fix the connection counter
The provided `Connection` class tracks connections incorrectly by updating an instance variable instead of the class variable. Modify the `__init__` method so it properly increments the shared `total_connections` class variable.
Challenge
Fix the Shared Cache Bug
The `Route` class below uses a class variable to store cached responses. Unfortunately, every route is sharing the exact same cache dictionary! Fix it by moving the cache to be an instance variable so each route has its own empty dictionary when created.
Continue learning
Previous: Class Creation and Instantiation