Technology  /  Python

๐Ÿ Python 78 guides ยท updated 2026

From first variable to OOP, generators, and real projects โ€” the language that runs everything from data pipelines to AI agents, taught the practical way.

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 Airways
print(t2.airline) # SkyRoute Airways
print(t1.total_price()) # 336.0

When you change the class variable through the class name, every instance sees the change:

FlightTicket.tax_rate = 0.15
print(t1.total_price()) # 345.0
print(t2.total_price()) # 920.0

That 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 only
print(t1.tax_rate) # 0.05 โ€” instance variable
print(t2.tax_rate) # 0.12 โ€” still sees the class variable
print(FlightTicket.tax_rate) # 0.12 โ€” class variable unchanged

To 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: 1
c2 = DatabaseConnection("db02.example.com") # Active: 2
print(DatabaseConnection.connection_count()) # 2
c1.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=True
Config.set_production()
Config.show() # env=production, debug=False

cls 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 object
e1 = 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.0

The 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 parametercls (the class)None
Access to classYes, through clsNo
Typical useFactory methods, shared stateUtility functions grouped with a class
Inheritance-awareYes โ€” cls is the calling classNo

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