PYTHON / OBJECT-ORIENTED PYTHON
Class methods and static methods
Write @classmethod alternative constructors and @staticmethod helpers, and predict exactly what each one receives when called.
What you will learn
- Use @classmethod with cls(...) so factories still work in subclasses
- Know that @staticmethod gets no implicit first argument at all
- Predict what cls is when a classmethod is called on a subclass or an instance
- Spot when cls.attr = ... silently shadows a base class attribute
Understanding Class methods and static methods
Every function you write in a class body is stored in the class, and what happens when you access it through a class or an instance is decided by the descriptor protocol. A plain function binds the object you looked it up on as the first argument, which is why instance methods get self. The @classmethod decorator changes that binding so the first argument is the class instead, and @staticmethod removes the binding entirely, so the function is handed back unchanged and receives only the arguments you pass.
The most common real use for @classmethod is an alternative constructor: a method that takes some other representation of the data, converts it, and returns cls(...). Writing cls(...) instead of hard-coding the class name matters because when a subclass calls the same method, cls is the subclass, so the factory produces the right type without being rewritten. That same binding rule means a classmethod called on an instance still receives the class, never the instance, so you cannot read instance attributes from inside one.
@staticmethod is for functions that logically belong to the class but need neither the instance nor the class: a validator, a small conversion, a predicate about raw values. Because nothing is bound, calling it on the class and calling it on an instance behave identically, and subclasses can override it like any other attribute. If a helper needs the class, for example to look at a class attribute or to construct instances, it wants @classmethod rather than @staticmethod.
class Temperature:
def __init__(self, kelvin):
self.kelvin = kelvin
classmethod
def from_celsius(cls, degrees):
return cls(degrees + 273.15)
staticmethod
def is_below_absolute_zero(kelvin):
return kelvin < 0
def __repr__(self):
return f"{type(self).__name__}({self.kelvin})"
class Reading(Temperature):
pass
t = Temperature.from_celsius(25)
r = Reading.from_celsius(0)
print(t)
print(r)
print(Temperature.is_below_absolute_zero(-5))
print(t.is_below_absolute_zero(t.kelvin))@classmethod binds the class as the first argument and @staticmethod binds nothing, which is why cls(...) makes factories subclass-aware and static helpers stay independent of any object.
Worked examples
cls is the class you called it on
Shows that a classmethod receives the subclass when called through the subclass, and still receives the class when called through an instance.
class Config:
def __init__(self, name):
self.name = name
classmethod
def describe(cls):
return f"class={cls.__name__}"
class DevConfig(Config):
pass
c = DevConfig("local")
print(c.describe())
print(Config.describe())
print(DevConfig.describe.__self__ is DevConfig)Example explained
Line 1c.describe() passes type(c), which is DevConfig, so cls.__name__ is DevConfig, not Config.
Line 2Config.describe() looks the method up on Config, so cls is Config even though DevConfig inherits it.
Line 3A bound classmethod stores the class in __self__, which is why it is DevConfig here and not the instance c.
cls.attr = ... writes to the subclass
Demonstrates that assigning through cls inside a classmethod creates a shadowing attribute on the subclass instead of updating the base class.
class Widget:
count = 0
def __init__(self):
Widget.count += 1
classmethod
def reset(cls):
cls.count = 0
classmethod
def how_many(cls):
return cls.count
class Gadget(Widget):
pass
Widget()
Gadget()
print(Widget.how_many(), Gadget.how_many())
Gadget.reset()
print(Widget.how_many(), Gadget.how_many())Example explained
Line 1__init__ names Widget explicitly, so both objects increment the single counter on Widget.
Line 2Before the reset, Gadget has no count of its own, so Gadget.how_many() reads Widget.count and both print 2.
Line 3Gadget.reset() runs with cls set to Gadget, and the assignment creates a new Gadget.count that hides the inherited one.
Line 4Widget.count is untouched, so the two classes now report different totals.
A staticmethod is just a function in the class
Shows a staticmethod used as a namespaced helper and what the attribute actually is before and after descriptor lookup.
class Parser:
staticmethod
def normalize(token):
return token.strip().lower()
def parse(self, line):
return [self.normalize(t) for t in line.split(",")]
p = Parser()
print(p.parse(" A , b ,C "))
print(Parser.normalize(" MiXeD "))
print(type(Parser.__dict__["normalize"]))
print(type(Parser.normalize))Example explained
Line 1self.normalize(t) passes only t, because a staticmethod adds no implicit first argument.
Line 2Parser.normalize(" MiXeD ") is the same call through the class, proving the lookup route does not matter.
Line 3The raw class dictionary holds a staticmethod object, the wrapper created by the decorator.
Line 4Reading Parser.normalize runs that wrapper's __get__, which hands back the plain function untouched.
Important notes
A staticmethod cannot see the class it is defined in, so it cannot construct instances or read class attributes without naming the class explicitly, which defeats the point of putting it there.
Calling the wrapper straight out of the class dictionary, as in Parser.__dict__["normalize"]("x"), only works on Python 3.10 and later; earlier versions raise TypeError because staticmethod objects were not callable.
Common mistakes
Writing an alternative constructor without the @classmethod decorator: Temperature.from_celsius(25) then binds cls to 25, and cls(...) raises TypeError: 'int' object is not callable.
Hard-coding the class name inside the factory, as in return Temperature(...), so Reading.from_celsius(0) returns a Temperature and every subclass loses its own type and methods.
Using cls.count += 1 in a classmethod to update a shared counter: the read comes from the base class but the write lands on the subclass, so the subclass silently gets its own counter.
Try it yourself
Change, predict, then run
Write a Duration class holding seconds, a @staticmethod looks_like_clock(text) that returns True for strings of the form "MM:SS", and a @classmethod from_string(text) that validates with that helper and returns cls(minutes * 60 + seconds). Subclass it as Timeout and check that Timeout.from_string("01:30") produces a Timeout, not a Duration.
Open the Python workspaceCheck your understanding
An alternative constructor is written as a classmethod that ends with return cls(value). Why is cls(value) preferred over naming the class directly?
- Because cls is the class the method was called on, so a subclass factory returns an instance of the subclass instead of the base class
- Because cls(value) skips the attribute lookup for the class name and is therefore faster
- Because __init__ cannot be reached when the class is referenced by its own name inside its body
- Because cls(value) bypasses __init__, so the returned object avoids the base class validation
Show answer
The classmethod binding puts the class the lookup went through into cls, so Timeout.from_string(...) builds a Timeout while a hard-coded name would always build the base class. The claim about __init__ being unreachable is wrong: the class name is available at call time, it just freezes the factory to one type, and cls(value) calls __init__ exactly like any other construction.