W3docs

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 @staticmethod vs @classmethod vs 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 parameterself (the instance)cls (the class)none
Receives the instance?YesNoNo
Receives the class?Via type(self)Yes (directly)No
Called on an instanceYesYesYes
Called on the classYes (but self is missing)YesYes
Typical useOperate on instance dataFactory methods, class-level stateUtility/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)) # True

When 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))             # False

Notice _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 within Temperature), 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())  # 0

Factory 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)  # 1

Calling 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 -5

Quick-Reference: Which Decorator Should I Use?

SituationRecommendation
The method reads or writes selfRegular 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'>  — correct

Always 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 cls

If 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=Child

Notice 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 self and has full access to the object's state.
  • A @classmethod receives cls — the class itself — instead of an instance. Use it for factory methods and anything that operates on class-level state. Always use cls(...) inside it so subclasses work correctly.
  • A @staticmethod receives neither self nor cls. 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.

Was this page helpful?