Class Variables & Class Methods
Class attributes and @classmethod operate on the class itself rather than any one instance - useful for shared state, counters, and factory constructors that need to know which class they're building.
Alongside instance-level state, Python classes support attributes and methods that belong to the class as a whole. A class attribute is shared across all instances (until an instance shadows it); a @classmethod operates on the class object itself, receiving cls instead of self, which makes it the natural tool for factory constructors and anything that needs to work correctly even when a subclass is involved.
Class Attributes: Shared State
A class attribute is defined directly in the class body (not inside __init__). It lives on the class object and is shared by every instance that doesn't have its own instance attribute of the same name.
Because neither instance has its own 'company' instance attribute, both look it up on the class and see the shared, updated value.
class Employee:
company = 'Acme Corp' # class attribute - shared
def __init__(self, name: str) -> None:
self.name = name # instance attribute - per object
e1 = Employee('Alice')
e2 = Employee('Bob')
print(e1.company, e2.company) # Acme Corp Acme Corp
Employee.company = 'NewCo' # change on the class
print(e1.company, e2.company) # NewCo NewCo - both see the updateClass vs Instance State: The Shadowing Rule
Assigning self.attr = value creates a new instance attribute that shadows (hides) the class attribute of the same name for that instance only - it does not modify the class attribute.
Attribute lookup order for e1.company after shadowing {'name': 'Alice', 'company': 'Solo Inc'} {'company': 'NewCo',...}
e1.company = 'Solo Inc' # creates an instance attribute on e1 only
print(e1.company) # 'Solo Inc' - e1's own attribute
print(e2.company) # 'NewCo' - unaffected, still reads the class attribute
print(Employee.company) # 'NewCo' - the class attribute itself is untouchedMutable Class Attributes: A Common Trap
If a class attribute is mutable (like a list or dict) and a method mutates it in place (rather than reassigning it), the shadowing rule above doesn't protect you - all instances see the mutation, since they all still reference the exact same object.
self.members.append(...) does not assign to self.members, so no instance attribute is created; it mutates the one shared list object.
class Team:
members = [] # shared mutable class attribute
def add(self, name: str) -> None:
self.members.append(name) # mutates the SHARED list - no shadowing happens
a, b = Team(), Team()
a.add('Alice')
print(b.members) # ['Alice'] - unexpectedly shared@classmethod and cls
A method decorated with @classmethod receives the class (cls) as its first argument instead of an instance. It can be called on the class directly, or on an instance (Python still passes the class, not the instance).
class Employee:
company = 'Acme Corp'
def __init__(self, name: str) -> None:
self.name = name
@classmethod
def rename_company(cls, new_name: str) -> None:
cls.company = new_name
Employee.rename_company('Globex')
print(Employee.company) # GlobexFactory Methods: The Main Use Case
The most common and idiomatic use of @classmethod is a factory method - an alternate constructor that builds and returns an instance of cls, often from a different input format than __init__ expects.
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
@classmethod
def from_tuple(cls, coords: tuple[float, float]) -> 'Point':
return cls(coords[0], coords[1])
@classmethod
def origin(cls) -> 'Point':
return cls(0, 0)
p1 = Point.from_tuple((3, 4))
p2 = Point.origin()
print(p1.x, p1.y, p2.x, p2.y) # 3 4 0 0Why cls Instead of the Class Name Directly
Using cls(...) rather than hardcoding Point(...) means the factory method correctly builds an instance of a *subclass* when called on that subclass - cls is bound to whichever class the method was actually invoked through.
If origin() had hardcoded 'return Point(0, 0)' instead of 'return cls(0, 0)', calling Point3D.origin() would incorrectly return a plain Point, not a Point3D.
class Point3D(Point):
def __init__(self, x: float, y: float, z: float = 0) -> None:
super().__init__(x, y)
self.z = z
p3 = Point3D.origin()
print(type(p3)) # <class '__main__.Point3D'> - origin() used cls, so this works correctlyclassmethod Called on an Instance
A @classmethod can be called through an instance too, but cls is still bound to the instance's class - never to the instance itself.
p = Point(1, 1)
p.rename = classmethod # (illustrative only)
print(Point.origin() is p) # False - unrelated new instance
# calling via instance still passes the class, not the instance:
same_origin = p.origin()
print(type(same_origin)) # <class '__main__.Point'>Class Methods vs Instance Methods vs Static Methods (Overview)
Instance methods (plain def in a class) receive self and operate on one instance's state. Class methods (@classmethod) receive cls and operate on the class itself, independent of any particular instance. Static methods (@staticmethod) receive neither and are just namespaced functions - covered fully in the Static Methods topic.
def method(self, ...) instance method - bound to one object, via self
@classmethod
def method(cls, ...) class method - bound to the class, via cls
@staticmethod
def method(...) static method - bound to neitherPractical Usage: Counting Instances
A class attribute paired with a class method is a common way to track information across all instances, such as a running instance count.
class Widget:
_count = 0
def __init__(self) -> None:
Widget._count += 1
@classmethod
def instance_count(cls) -> int:
return cls._count
Widget(); Widget(); Widget()
print(Widget.instance_count()) # 3Key Takeaways
A class attribute is defined in the class body and shared by all instances unless an instance shadows it with its own attribute of the same name.
Assigning self.attr = value always creates/updates an instance attribute; it never modifies the class attribute, but mutating a shared mutable class attribute in place affects every instance.
@classmethod methods receive cls (the class) instead of self, and can be called on the class or an instance - cls is always the class, never the instance.
The primary use case for classmethod is a factory method - use cls(...) rather than the hardcoded class name so subclasses build correctly.
Class state is for information that belongs to the type as a whole (shared counters, configuration, alternate constructors); instance state is for per-object data.
class Team: members = [] def add(self, name): self.members.append(name) # mutates shared class list class Team: def __init__(self): self.members = [] # instance attribute, independent per Team def add(self, name): self.members.append(name)
Appending to a shared mutable class attribute affects every instance; give each instance its own list in __init__ instead.
class Point: @classmethod def origin(cls): return Point(0, 0) # hardcodes the base class class Point: @classmethod def origin(cls): return cls(0, 0) # respects subclasses
Hardcoding the class name inside a factory classmethod breaks correct behavior for subclasses, which should get an instance of themselves, not the base class.
class Config: DEFAULT_TIMEOUT = 30 def set_timeout(self, value): self.DEFAULT_TIMEOUT = value # silently creates a shadowing instance attribute, not a real update class Config: DEFAULT_TIMEOUT = 30 @classmethod def set_default_timeout(cls, value): cls.DEFAULT_TIMEOUT = value # actually updates the shared class attribute self.attr = value never updates a class attribute of the same name; it shadows it on that one instance. To genuinely change shared class state, assign via the class or a classmethod using cls.
What is the difference between a class attribute and an instance attribute?
A class attribute is defined in the class body and shared by all instances (looked up on the class when an instance doesn't have its own); an instance attribute is set per-object (typically via self.attr = value in __init__) and is unique to that instance.
Why does self.attr = value never modify a class attribute of the same name?
Because assignment through self always creates or updates an attribute on that specific instance's own namespace, shadowing (not altering) any class attribute of the same name.
Why is a mutable class attribute like a shared list dangerous?
Because mutating it in place (e.g. .append()) - rather than reassigning it - changes the one object every instance references, so the change is visible to all instances, which is usually not the intended behavior.
What does a @classmethod receive as its first parameter, and how does that differ from an instance method?
It receives cls, the class itself, whereas an instance method receives self, the specific instance it was called on.
Why is cls(...) preferred over hardcoding the class name inside a factory classmethod?
Because cls is bound to whatever class the method was actually called through, so a subclass calling the inherited factory method correctly gets an instance of the subclass rather than the base class.
Interview: if a classmethod is called through an instance rather than the class, what gets bound to cls?
Still the class of that instance, never the instance itself - classmethods always receive the class, regardless of whether they're invoked via ClassName.method() or instance.method().
Give a practical use case for a class attribute plus a classmethod together.
Tracking shared state across all instances, such as a running count of how many instances have been created (a class attribute incremented in __init__, exposed via a classmethod).
Write a class Currency with a class attribute exchange_rates (dict) and a classmethod set_rate(cls, code, rate) that updates it.
class Currency: exchange_rates: dict[str, float] = {} @classmethod def set_rate(cls, code: str, rate: float) -> None: cls.exchange_rates[code] = rate Currency.set_rate('EUR', 0.92) print(Currency.exchange_rates) # {'EUR': 0.92} cls.exchange_rates[code] = rate mutates the shared class-level dict directly via cls, so the update is visible through the class and every instance.
What does this print?
base: base sub: sub cls is bound to whichever class the method is called through, so Base.describe() sees cls.__name__='Base' and cls.label='base' (Base's own attribute), while Sub.describe() sees cls.__name__='Sub' and cls.label='sub' (Sub's overriding class attribute).
Write a class Pizza with a classmethod margherita(cls) that returns a Pizza instance pre-configured with toppings=['mozzarella', 'tomato'], using cls so subclasses work correctly.
class Pizza: def __init__(self, toppings: list[str]) -> None: self.toppings = toppings @classmethod def margherita(cls) -> 'Pizza': return cls(['mozzarella', 'tomato']) class StuffedCrustPizza(Pizza): pass m = Pizza.margherita() sc = StuffedCrustPizza.margherita() print(m.toppings, type(m).__name__) # ['mozzarella', 'tomato'] Pizza print(sc.toppings, type(sc).__name__) # ['mozzarella', 'tomato'] StuffedCrustPizza
Because margherita() calls cls(...) instead of hardcoding Pizza(...), StuffedCrustPizza.margherita() correctly returns a StuffedCrustPizza instance rather than a plain Pizza.
class Base:
label = 'base'
@classmethod
def describe(cls):
return f'{cls.__name__}: {cls.label}'
class Sub(Base):
label = 'sub'
print(Base.describe())
print(Sub.describe())