Every method inside a Python class falls into one of three categories, each expressing a genuinely different relationship to the class it belongs to. This article covers the python class method (@classmethod) and python static method (@staticmethod) decorators alongside the familiar default — instance methods — and gives you a clear, practical way to decide which one fits a given method.
Introduction: Three Kinds of Methods, Three Relationships
Python classes support three kinds of methods: instance methods (the default, no decorator needed), class methods (@classmethod), and static methods (@staticmethod). Each one expresses a different relationship between the method and the class it's defined on.
The key question to ask
When writing a method, ask yourself: does it need self (a specific instance's data)? Does it need cls (the class itself, but not any particular instance)? Or does it need neither?
A quick preview table
Method type | First parameter | Access to |
|---|---|---|
Instance method |
| The specific instance, and through it, the class |
Class method |
| The class itself, not any specific instance |
Static method | (none) | Neither — essentially a plain function |
The rest of this article walks through each in detail, then closes with a practical decision guide.
Instance Methods: The Default
The familiar pattern
This is the pattern you've used throughout every earlier article in this series involving classes: a method whose first parameter is self, giving it access to that specific object's data (and, through the instance, the class it belongs to as well):
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hi, I'm {self.name} and I'm {self.age} years old"
alex = Person("Alex", 30)
print(alex.greet()) # Hi, I'm Alex and I'm 30 years oldgreet() reads self.name and self.age, which only exist on a specific instance — this is exactly the situation instance methods are built for.
Why this is the right choice for per-object data
Any time a method genuinely needs to read or modify data unique to a specific object — as covered in the earlier instance vs. class variables article — an instance method, with self as its first parameter, is the correct and default choice. This covers the overwhelming majority of methods you'll write on any given class.
Class Methods with @classmethod
First parameter: cls
A class method's first parameter is conventionally named cls, and it refers to the class itself — not any specific instance. Class methods can access class-level data (and the class itself, including its other class methods), but they don't automatically have access to any particular object's instance attributes.
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
@classmethod
def get_population(cls):
return cls.population
Person("Alex")
Person("Sam")
print(Person.get_population()) # 2get_population() never needs self, since it's not reading or modifying anything specific to one instance — it's operating purely on class-level, shared data.
The most common real-world use: alternative constructors
This is genuinely the pattern you'll encounter most often for @classmethod in real code: building and returning an instance in a way that's different from the standard __init__ signature — often called a factory method or alternative constructor:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_string(cls, person_string):
name, age = person_string.split(",")
return cls(name.strip(), int(age.strip()))
alex = Person.from_string("Alex, 30")
print(alex.name, alex.age) # Alex 30from_string() offers a genuinely different way to construct a Person — from a single formatted string, rather than separate name and age arguments — without needing to cram that parsing logic into __init__ itself, which would make __init__ messier and less focused, exactly the guidance given in the earlier __init__ article about keeping constructors lightweight.
The critical inheritance detail: cls(...), not the hardcoded class name
This is genuinely important, and easy to get subtly wrong: inside a classmethod, always construct the new instance using cls(...), never by hardcoding the literal class name directly. Using cls(...) ensures that subclasses correctly get an instance of themselves, not of the base class:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_string(cls, person_string):
name, age = person_string.split(",")
return cls(name.strip(), int(age.strip())) # correct — uses cls
class Employee(Person):
def __init__(self, name, age, employee_id):
super().__init__(name, age)
self.employee_id = employee_id
# Even though from_string() is inherited from Person, calling it on Employee
# correctly produces an Employee instance, because it uses cls(...) internallyIf from_string() had hardcoded return Person(...) instead of return cls(...), calling Employee.from_string(...) would incorrectly return a plain Person object — silently discarding the fact that it was called on the Employee subclass, and losing access to anything Employee adds on top. Using cls is what makes factory methods correctly inheritance-aware.
Static Methods with @staticmethod
No automatic first parameter at all
A static method receives no automatic first parameter — no self, no cls. It's essentially a plain, ordinary function that happens to be namespaced inside a class for organizational purposes, and it has no automatic access to either instance or class data:
class MathUtils:
@staticmethod
def add(a, b):
return a + b
print(MathUtils.add(3, 5)) # 8 — called without needing an instance at allGood use cases
Static methods fit utility or helper logic that conceptually belongs with a class — thematically related to what the class represents — but genuinely doesn't need any instance or class state to do its job:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@staticmethod
def celsius_to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
@staticmethod
def is_valid_temperature(celsius):
return celsius >= -273.15 # absolute zero
print(Temperature.celsius_to_fahrenheit(100)) # 212.0
print(Temperature.is_valid_temperature(-300)) # FalseBoth of these methods are thematically tied to temperature conversion and validation — a natural fit to live inside Temperature — but neither needs self.celsius or any other instance-specific state to compute their result; they operate purely on whatever arguments are passed in directly.
The benefit beyond organization
Marking a method @staticmethod isn't purely about tidiness — it communicates something concrete to anyone reading the code: calling this method is guaranteed not to touch any object's state, since it has no automatic access to self or cls at all. This is a genuinely useful signal when scanning through a class's methods trying to understand what might or might not have side effects on a specific object. It also makes the method trivially easy to test in complete isolation — no need to construct an instance of the class at all just to verify the static method's behavior:
# Testing celsius_to_fahrenheit requires zero setup — no Temperature instance needed
assert Temperature.celsius_to_fahrenheit(0) == 32
assert Temperature.celsius_to_fahrenheit(100) == 212Choosing the Right One: A Practical Decision Guide
A simple heuristic
Needs
self(reads or modifies a specific instance's data) → instance method.Needs
cls, or specifically needs to create and return a new instance of the class → class method.Needs neither → static method — or consider whether it might make more sense as a plain, module-level function instead, if it's genuinely general-purpose and doesn't have any strong conceptual tie to the class at all.
A common mistake: using @staticmethod for factory methods
This is worth calling out explicitly, since it's a genuinely easy mistake to make: using @staticmethod for something that's really a factory-style constructor, rather than @classmethod:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def from_string(person_string): # should be @classmethod, not @staticmethod
name, age = person_string.split(",")
return Person(name.strip(), int(age.strip())) # hardcoded class name — breaks for subclassesThis version works correctly when called directly on Person, but it breaks the exact inheritance scenario covered in Section 3 — calling Employee.from_string(...) would still incorrectly return a plain Person, since there's no cls available to reference the actual calling class. Any factory-style method meant to construct and return an instance should almost always be a @classmethod, using cls(...), specifically so it continues to behave correctly across any future subclasses.
A worked example combining all three
import re
class User:
total_users = 0
def __init__(self, username, email):
self.username = username
self.email = email
User.total_users += 1
def greet(self): # instance method — needs self
return f"Welcome, {self.username}!"
@classmethod
def create_admin(cls, username): # class method — creates a new instance via cls
return cls(username, f"{username}@admin.example.com")
@staticmethod
def validate_email(email): # static method — no instance or class data needed
return bool(re.match(r"^[^@]+@[^@]+\.[^@]+$", email))
user = User("alex99", "[email protected]")
print(user.greet()) # Welcome, alex99!
admin = User.create_admin("root")
print(admin.email) # [email protected]
print(User.validate_email("not-an-email")) # False
print(User.total_users) # 2This single class demonstrates the full range covered in this article: greet() needs self to reference the specific user's name, create_admin() needs cls to correctly build and return a new instance (correctly, even for any future subclasses of User), and validate_email() needs neither — it's a pure, standalone piece of logic that just happens to belong conceptually alongside the rest of the User class's responsibilities.