Python @staticmethod and @classmethod
Learn how Python's @staticmethod and @classmethod decorators work, when to use each, and how to write factory methods and utility helpers with examples.
Python gives every method inside a class one of three binding styles: it can be bound to an instance, to the class itself, or to neither. The @classmethod and @staticmethod decorators control these last two styles.
This chapter covers:
- The three method types and what sets them apart
@staticmethod— a plain function stored inside a class@classmethod— a method that receives the class as its first argument- Factory methods: the most common real-world use of
@classmethod - Alternative constructors and how they interact with inheritance
- When to choose
@staticmethodvs@classmethodvs a module-level function - Common pitfalls
Before reading, make sure you are comfortable with Python classes and objects and Python inheritance. For computed attribute access, see @property. For a deep dive into how decorators work in general, see Python Decorators.
The Three Method Types
Before looking at each decorator, here is a side-by-side comparison:
| Instance method | @classmethod | @staticmethod | |
|---|---|---|---|
| First parameter | self (the instance) | cls (the class) | none |
| Receives the instance? | Yes | No | No |
| Receives the class? | Via type(self) | Yes (directly) | No |
| Called on an instance | Yes | Yes | Yes |
| Called on the class | Yes (but self is missing) | Yes | Yes |
| Typical use | Operate on instance data | Factory methods, class-level state | Utility/helper functions |
class Demo:
def instance_method(self):
return f"instance method — self is {self}"
@classmethod
def class_method(cls):
return f"class method — cls is {cls}"
@staticmethod
def static_method():
return "static method — no self, no cls"
d = Demo()
print(d.instance_method()) # instance method — self is <__main__.Demo object at 0x...>
print(d.class_method()) # class method — cls is <class '__main__.Demo'>
print(d.static_method()) # static method — no self, no cls
# All three can also be called directly on the class:
print(Demo.class_method()) # class method — cls is <class '__main__.Demo'>
print(Demo.static_method()) # static method — no self, no cls@staticmethod
A static method is the simplest of the three. It is just a regular function that happens to live inside a class namespace. Python does not pass self or cls automatically.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(n):
return n % 2 == 0
print(MathUtils.add(3, 4)) # 7
print(MathUtils.is_even(10)) # TrueWhen to use @staticmethod
Use @staticmethod when a helper logically belongs to a class — for clarity, grouping, or namespacing — but does not need to read or modify either instance state or class state:
- Validation helpers called before constructing an object.
- Pure conversion or calculation functions that are only meaningful in the context of one class.
- Utility functions used by several methods of the same class but nowhere else.
class Temperature:
def __init__(self, celsius):
if not Temperature._is_valid(celsius):
raise ValueError(f"Temperature {celsius} °C is below absolute zero")
self.celsius = celsius
@staticmethod
def _is_valid(celsius):
return celsius >= -273.15
@staticmethod
def celsius_to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
t = Temperature(100)
print(Temperature.celsius_to_fahrenheit(100)) # 212.0
print(Temperature._is_valid(-300)) # FalseNotice _is_valid is prefixed with _ to signal it is internal to the class. Callers that only need Temperature objects never see it — they just get a ValueError if they pass an impossible value.
@staticmethod vs a module-level function
A module-level function and a @staticmethod are almost identical in behavior. The difference is where the function lives:
- If the function is only relevant to
Temperature(or is called exclusively from withinTemperature), put it inside the class as a@staticmethod. - If it is a general utility used across your module, put it at module level.
There is no performance difference. This is purely an organizational choice.
@classmethod
A class method receives the class as its first argument (named cls by convention — but just like self, the name is a convention, not a keyword). Because it has a reference to the class, it can:
- Read or modify class-level attributes.
- Create and return new instances of the class (factory methods).
- Work correctly with subclasses (polymorphic factories).
class Counter:
_count = 0 # class-level attribute
def __init__(self):
Counter._count += 1
@classmethod
def get_count(cls):
return cls._count
@classmethod
def reset(cls):
cls._count = 0
Counter()
Counter()
Counter()
print(Counter.get_count()) # 3
Counter.reset()
print(Counter.get_count()) # 0Factory methods — the most important use case
The most common and valuable use of @classmethod is as a factory method (also called an alternative constructor). A factory method creates instances from different kinds of input without cluttering __init__ with conditional logic.
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"Date({self.year}, {self.month}, {self.day})"
@classmethod
def from_string(cls, date_string):
"""Create a Date from an ISO 8601 string, e.g. '2024-03-15'."""
year, month, day = (int(p) for p in date_string.split("-"))
return cls(year, month, day)
@classmethod
def from_tuple(cls, date_tuple):
"""Create a Date from a (year, month, day) tuple."""
return cls(*date_tuple)
d1 = Date(2024, 3, 15)
d2 = Date.from_string("2024-03-15")
d3 = Date.from_tuple((2024, 3, 15))
print(d1) # Date(2024, 3, 15)
print(d2) # Date(2024, 3, 15)
print(d3) # Date(2024, 3, 15)__init__ stays simple — it just stores three integers. The class methods handle the conversion logic. This is cleaner than a single __init__ with multiple optional parameters and if/elif branches.
Why cls matters for inheritance
When a factory class method calls cls(...) instead of hard-coding the class name, it creates an instance of whatever class the method was called on — even a subclass. This is why you should always prefer cls(...) over ClassName(...) inside a @classmethod.
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"
@classmethod
def from_string(cls, date_string):
year, month, day = (int(p) for p in date_string.split("-"))
return cls(year, month, day) # uses cls, not Date
class DateTime(Date):
pass # inherits from_string
dt = DateTime.from_string("2024-03-15")
print(dt) # DateTime(2024, 3, 15) — correct subclass
print(type(dt)) # <class '__main__.DateTime'>If from_string had hard-coded return Date(year, month, day), calling DateTime.from_string(...) would return a Date, not a DateTime — quietly breaking the inheritance contract.
Modifying class-level state
Class methods can also act as named constructors with side effects, or they can manipulate class variables that track shared state:
class Registry:
_instances = []
def __init__(self, name):
self.name = name
Registry._instances.append(self)
@classmethod
def all(cls):
return list(cls._instances)
@classmethod
def clear(cls):
cls._instances.clear()
Registry("alice")
Registry("bob")
Registry("carol")
print([r.name for r in Registry.all()]) # ['alice', 'bob', 'carol']
Registry.clear()
print(Registry.all()) # []Calling from an Instance vs the Class
Both @staticmethod and @classmethod can be called on either an instance or the class. Python handles either form:
class Circle:
PI = 3.14159265
def __init__(self, radius):
self.radius = radius
def area(self):
return Circle.PI * self.radius ** 2
@classmethod
def unit_circle(cls):
"""Return a circle with radius 1."""
return cls(1)
@staticmethod
def describe():
return "A circle is a round plane figure."
c = Circle(5)
# staticmethod — callable on instance or class
print(c.describe()) # A circle is a round plane figure.
print(Circle.describe()) # A circle is a round plane figure.
# classmethod — callable on instance or class
unit = c.unit_circle()
print(unit.radius) # 1
print(Circle.unit_circle().radius) # 1Calling them on the class is usually clearer — it signals to the reader that no instance data is involved.
Combining @classmethod and @staticmethod
A class method may delegate validation work to a static method, because the class method has access to cls to call it:
class PositiveNumber:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"PositiveNumber({self.value})"
@staticmethod
def _validate(value):
if value <= 0:
raise ValueError(f"Expected a positive number, got {value!r}")
@classmethod
def create(cls, value):
cls._validate(value)
return cls(value)
n = PositiveNumber.create(42)
print(n) # PositiveNumber(42)
try:
PositiveNumber.create(-5)
except ValueError as e:
print(e) # Expected a positive number, got -5Quick-Reference: Which Decorator Should I Use?
| Situation | Recommendation |
|---|---|
The method reads or writes self | Regular instance method |
| The method creates a new instance | @classmethod (factory / alternative constructor) |
| The method reads or writes a class attribute | @classmethod |
| The method is a pure helper that needs no class or instance data | @staticmethod (or module-level function) |
| The method validates input before construction | @staticmethod |
| The method should work correctly in subclasses | @classmethod (use cls, not the hard-coded class name) |
Common Pitfalls
Forgetting cls inside @classmethod
If you hard-code the class name instead of using cls, inheritance breaks silently:
class Animal:
@classmethod
def create(cls):
return cls() # correct — returns an instance of the actual class
class Dog(Animal):
pass
print(type(Dog.create())) # <class '__main__.Dog'> — correctAlways use cls(...), never Animal(...), inside a class method.
Accessing self or cls in a @staticmethod
A @staticmethod receives no implicit first argument. Attempting to reference self or cls inside it is an error:
class Bad:
label = "bad"
@staticmethod
def show():
# print(cls.label) # NameError: name 'cls' is not defined
print("use @classmethod if you need cls")
Bad.show() # use @classmethod if you need clsIf you find yourself needing cls in what you thought was a static method, switch it to a @classmethod.
Mixing up the decorators
@classmethod methods must have cls as the first explicit parameter, and @staticmethod methods must have none. Swapping them causes a TypeError at call time, not at definition time — which can be surprising:
class Broken:
@staticmethod
def forgot_cls(cls): # cls is just a regular positional argument here
return cls
# Broken.forgot_cls() # TypeError: forgot_cls() missing 1 required positional argument: 'cls'Overriding in subclasses
Both decorators work with super() and can be overridden:
class Base:
@classmethod
def who(cls):
return f"Base.who called with cls={cls.__name__}"
class Child(Base):
@classmethod
def who(cls):
parent = super().who()
return f"Child.who — parent said: {parent}"
print(Child.who())
# Child.who — parent said: Base.who called with cls=ChildNotice cls in Base.who is still Child — because the method was dispatched from Child.
Real-World Example: A User Class
Here is a complete example that brings together instance methods, a class method factory, and a static method validator:
import re
class User:
_all_users = []
def __init__(self, name, email):
User._validate_email(email)
self.name = name
self.email = email
User._all_users.append(self)
def __repr__(self):
return f"User(name={self.name!r}, email={self.email!r})"
# --- instance method ---
def greet(self):
return f"Hello, my name is {self.name}."
# --- factory / alternative constructor ---
@classmethod
def from_dict(cls, data):
"""Create a User from a dict like {'name': 'Alice', 'email': '[email protected]'}."""
return cls(data["name"], data["email"])
# --- class-level query ---
@classmethod
def count(cls):
return len(cls._all_users)
# --- pure helper, no instance or class data needed ---
@staticmethod
def _validate_email(email):
pattern = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$"
if not re.match(pattern, email):
raise ValueError(f"Invalid email address: {email!r}")
# Create via normal constructor
u1 = User("Alice", "[email protected]")
# Create via factory
u2 = User.from_dict({"name": "Bob", "email": "[email protected]"})
print(u1.greet()) # Hello, my name is Alice.
print(u2.greet()) # Hello, my name is Bob.
print(User.count()) # 2
try:
User("Carol", "not-an-email")
except ValueError as e:
print(e) # Invalid email address: 'not-an-email'This pattern — __init__ for normal construction, @classmethod for alternate constructors, @staticmethod for helpers — appears throughout the Python standard library (see datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).
Summary
- An instance method receives
selfand has full access to the object's state. - A
@classmethodreceivescls— the class itself — instead of an instance. Use it for factory methods and anything that operates on class-level state. Always usecls(...)inside it so subclasses work correctly. - A
@staticmethodreceives neitherselfnorcls. Use it for pure utility logic that belongs in the class namespace but needs no object or class data.
For computed attributes that look like regular attribute access, see @property. For the full decorator mechanism that makes all three work, see Python Decorators.