Class Variables and @classmethod in Python: Shared State and Alternative Constructors
Some data belongs to a class as a whole, not to any single instance. Some operations make sense at the class level rather than on an individual object. Python handles both of these through class variables and the @classmethod decorator.
Class Variables: Data Shared Across All Instances
A class variable is defined directly in the class body, outside any method. Every instance of the class has access to it through the same reference.
class FlightTicket: airline = "SkyRoute Airways" # class variable tax_rate = 0.12 # class variable
def __init__(self, destination, base_price): self.destination = destination # instance variable self.base_price = base_price # instance variable
def total_price(self): return self.base_price * (1 + self.tax_rate)
t1 = FlightTicket("Paris", 300)t2 = FlightTicket("Tokyo", 800)
print(t1.airline) # SkyRoute Airwaysprint(t2.airline) # SkyRoute Airwaysprint(t1.total_price()) # 336.0When you change the class variable through the class name, every instance sees the change:
FlightTicket.tax_rate = 0.15
print(t1.total_price()) # 345.0print(t2.total_price()) # 920.0That is the key trait: one value, shared everywhere.
The Shadow Trap
A common mistake is assigning a class variable through an instance. This does not modify the class variable โ it creates a new instance variable on that specific object, hiding the class variable from that instance only.
FlightTicket.tax_rate = 0.12 # reset
t1.tax_rate = 0.05 # creates an instance variable on t1 onlyprint(t1.tax_rate) # 0.05 โ instance variableprint(t2.tax_rate) # 0.12 โ still sees the class variableprint(FlightTicket.tax_rate) # 0.12 โ class variable unchangedTo update shared state, always go through the class: FlightTicket.tax_rate = 0.20.
Practical Use: Instance Counters
Class variables are a clean way to track how many objects exist.
class DatabaseConnection: _active_connections = 0 # underscore signals internal use
def __init__(self, host): self.host = host DatabaseConnection._active_connections += 1 print(f"Connected to {host}. Active: {DatabaseConnection._active_connections}")
def close(self): DatabaseConnection._active_connections -= 1 print(f"Closed {self.host}. Active: {DatabaseConnection._active_connections}")
@classmethod def connection_count(cls): return cls._active_connections
c1 = DatabaseConnection("db01.example.com") # Active: 1c2 = DatabaseConnection("db02.example.com") # Active: 2
print(DatabaseConnection.connection_count()) # 2c1.close() # Active: 1@classmethod: Methods That Operate on the Class
A class method receives the class itself as its first argument (conventionally named cls), not an instance. You apply the @classmethod decorator to define one.
class Config: _env = "development" _debug = True
@classmethod def set_production(cls): cls._env = "production" cls._debug = False
@classmethod def show(cls): print(f"env={cls._env}, debug={cls._debug}")
Config.show() # env=development, debug=TrueConfig.set_production()Config.show() # env=production, debug=Falsecls refers to the class the method is called on. This matters in inheritance โ if a subclass calls the method, cls is the subclass, not the parent.
The Factory Pattern: Alternative Constructors
The most common and useful application of @classmethod is building alternative constructors โ ways to create objects from different kinds of input.
from datetime import date
class Employee: def __init__(self, name, birth_year, salary): self.name = name self.birth_year = birth_year self.salary = salary
@classmethod def from_string(cls, employee_str): """Create an Employee from a 'name:birth_year:salary' string.""" name, birth_year, salary = employee_str.split(":") return cls(name, int(birth_year), float(salary))
@classmethod def from_dict(cls, data): """Create an Employee from a dictionary.""" return cls(data["name"], data["birth_year"], data["salary"])
@property def age(self): return date.today().year - self.birth_year
# Three ways to create the same kind of objecte1 = Employee("Alice", 1990, 75000)e2 = Employee.from_string("Bob:1985:82000")e3 = Employee.from_dict({"name": "Carol", "birth_year": 1992, "salary": 68000})
print(e2.name, e2.age) # Bob (age varies by current year)print(e3.salary) # 68000.0The main constructor (__init__) stays clean. Each factory method handles the conversion from a specific input format. Callers pick the constructor that matches their data.
@classmethod vs @staticmethod
They look similar but serve different purposes.
@classmethod | @staticmethod | |
|---|---|---|
| First parameter | cls (the class) | None |
| Access to class | Yes, through cls | No |
| Typical use | Factory methods, shared state | Utility functions grouped with a class |
| Inheritance-aware | Yes โ cls is the calling class | No |
Use @classmethod when the method needs to know what class it belongs to. Use @staticmethod when the function just happens to live in the class for organisational reasons.
When Class Variables Go Wrong
Mutable class variables shared across instances can cause surprising bugs.
class ShoppingCart: items = [] # Bug: shared across ALL carts!
def add(self, item): self.items.append(item)
cart1 = ShoppingCart()cart2 = ShoppingCart()
cart1.add("shoes")print(cart2.items) # ['shoes'] โ cart2 sees cart1's items!Fix: always initialise mutable data in __init__:
class ShoppingCart: def __init__(self): self.items = [] # each cart gets its own list
def add(self, item): self.items.append(item)Class variables work best for data that is genuinely shared: configuration constants, counters, caches, and registries.
Summary
- Class variables are shared across all instances; change them through the class name.
- Instance variables are unique per object; set them in
__init__(). @classmethodreceivesclsas its first argument and is aware of the class hierarchy.- Factory methods (
from_string,from_dict, etc.) are the most common use case for@classmethod. - Never use a mutable class variable (list, dict) as shared state unless you really mean it.