Three kinds of method, and the choice between them is not a style question. It is a question of what the method actually needs.
- An instance method needs this particular object.
- A classmethod needs the class.
- A staticmethod needs neither.
All three in one class
from datetime import date
class Employee:
raise_pct = 1.05
def __init__(self, name, salary):
self.name, self.salary = name, salary
def give_raise(self): # instance method: needs this employee
self.salary = round(self.salary * self.raise_pct, 2)
@classmethod
def from_csv(cls, line): # classmethod: needs the class
name, salary = line.split(',')
return cls(name, float(salary))
@staticmethod
def is_payday(d): # staticmethod: needs neither
return d.day == 25
e = Employee.from_csv('Ada,50000')
e.give_raise()
print(e.name, e.salary)
print(Employee.is_payday(date(2026, 9, 25)), Employee.is_payday(date(2026, 9, 3)))
It prints:
Ada 52500.0
True False
from_csv is the classic classmethod: an alternative constructor. Python has one __init__ per class, so every other way of building the object becomes a classmethod.
Why from_csv uses cls
This is the part that matters, and the reason a classmethod is not just a staticmethod that happens to receive the class.
# why from_csv uses cls and not Employee
class Contractor(Employee):
raise_pct = 1.10
c = Contractor.from_csv('Grace,60000')
print(type(c).__name__, c.name, c.salary)
c.give_raise()
print('after raise:', c.salary)
It prints:
Contractor Grace 60000.0
after raise: 66000.0
Contractor.from_csv produced a Contractor, not an Employee, and the raise used 1.10. Nobody wrote a from_csv on Contractor — cls was Contractor because that is what the call was made on.
Hard-code the class and that stops working:
# hard-coding the class breaks the subclass
class Broken(Employee):
@classmethod
def from_csv(cls, line):
name, salary = line.split(',')
return Employee(name, float(salary)) # wrong: always the base class
print(type(Broken.from_csv('Alan,70000')).__name__)
It prints:
Employee
Broken.from_csv returns an Employee. Every subclass silently gets the wrong type, and nothing raises. Alternative constructors use cls, always.
What each one receives
class Show:
def instance(self): return f"got {type(self).__name__} instance"
@classmethod
def klass(cls): return f"got the class {cls.__name__}"
@staticmethod
def static(): return "got nothing"
s = Show()
print(s.instance())
print(Show.klass())
print(Show.static())
It prints:
got Show instance
got the class Show
got nothing
When to use a staticmethod
Rarely, and the honest answer is: when a plain function would do but you want it namespaced with the class it relates to. is_payday does not touch an employee or the class — it is a function about dates.
If a staticmethod has nothing to do with the class either, it wants to be a module-level function. Putting it in a class buys you nothing but a longer name.
What to remember
-
Instance method for anything that reads or changes this object.
-
Classmethod for alternative constructors, and use
clsso subclasses work. -
Staticmethod when the method needs neither, and only when grouping it with the class genuinely helps.
-
Hard-coding the class name inside an alternative constructor is a bug that only shows up once someone subclasses you.