Python @classmethod vs @staticmethod: When to Use Each
Imagine a company with three kinds of employees. Regular employees work on their own tasks (they need to know about themselves). Managers can hire new employees for the whole department (they need to know about the department). Utility workers do general tasks like cleaning that don't depend on any specific person or department.
Python methods work the same way. Regular methods operate on a specific object (self). Class methods operate on the class itself (cls). Static methods don't need access to either — they're just helper functions that logically belong to the class.
In this tutorial, you'll learn exactly when and how to use @classmethod and @staticmethod, and the powerful factory method pattern that makes @classmethod so useful.
How Do Regular Instance Methods Work?
Before we learn the new method types, let's quickly recap regular methods. A regular method takes self as its first parameter and can access and modify the object's data.
The describe() method needs self to access self.size and self.toppings. This is the most common type of method, and it's what you've been writing so far.
What Is @classmethod and How Does It Work?
A class method receives the class itself as its first argument instead of an instance. You mark it with the @classmethod decorator, and by convention the first parameter is called cls (not self).
Notice that change_default_size uses cls.default_size instead of self.default_size. It modifies the class attribute, which affects all instances — not just one specific pizza.
What Are Factory Methods?
The most powerful use of @classmethod is the factory method pattern. A factory method is an alternative constructor — it gives you a different way to create objects besides calling ClassName() directly.
For example, what if you want to create a Date object from a string like '2024-01-15'? You can't change __init__ to handle both formats cleanly. Instead, you create a factory method.
The key line is return cls(int(year), int(month), int(day)). The factory method parses the string, then uses cls(...) to create a new instance. Using cls instead of Date means this will work correctly even with subclasses.
What Is @staticmethod and How Does It Work?
A static method doesn't receive self or cls. It's just a regular function that lives inside a class because it's logically related. It can't access or modify the instance or the class.
These methods don't need any data from an instance or the class. They're just utility functions grouped under MathHelper for organization. You call them on the class directly.
The validate_topping method doesn't need self or cls — it just checks a topping against a list. Making it a static method signals to other developers that this function is independent of any specific instance.
When Should You Use Each Method Type?
Here's the simple decision guide:
self). This is the default. Use it for reading or changing an object's data.cls). Use it for factory methods and modifying class-level state.self or cls. Use it for utility functions that logically belong to the class.# Needs self? -> Regular method
def get_area(self):
return self.width * self.height
# Needs cls? -> @classmethod
@classmethod
def from_square(cls, side):
return cls(side, side)# No self, no cls -> @staticmethod
@staticmethod
def is_valid_dimension(value):
return value > 0What Are Common Real-World Patterns?
Let's look at a realistic example. Imagine you're building a configuration system where settings can come from different sources — a dictionary, a JSON string, or default values.
Notice how from_json calls cls.from_dict internally. Factory methods can chain together, keeping each one simple and focused on a single conversion step.
Here is a quick summary of the three method types side by side:
# Regular method: operates on ONE specific object
# -> def method(self, ...)
# -> Access self.attribute, self.other_method()
# @classmethod: operates on the CLASS itself
# -> def method(cls, ...)
# -> Access cls.class_attribute, cls(...) to create instances
# @staticmethod: independent helper
# -> def method(...)
# -> No access to self or cls, just a utility functionPractice Exercises
Create a Person class with name and age attributes. Add a @classmethod called from_birth_year that takes name and birth_year, calculates the age (use 2024 as the current year), and returns a new Person.
Create a person using Person.from_birth_year('Alice', 2000) and print their name and age on separate lines.
Create a Password class with a value attribute. Add a @staticmethod called is_strong that takes a password string and returns True if it has 8 or more characters, False otherwise.
Print the result of Password.is_strong('abc') and Password.is_strong('securepass123').
Read the code and predict the output. Pay attention to which method type each is.
class Counter:
count = 0
def __init__(self):
Counter.count += 1
@classmethod
def get_count(cls):
return cls.count
@staticmethod
def info():
return 'I count things'
a = Counter()
b = Counter()
c = Counter()
print(Counter.get_count())
print(Counter.info())This Rectangle class has a factory method with two bugs. Fix them so the code prints:
5 x 5Create a Temperature class that stores temperature in Celsius:
1. __init__(self, celsius) stores the value
2. @classmethod from_fahrenheit(cls, f) — creates from Fahrenheit: (f - 32) * 5 / 9
3. @staticmethod is_freezing(celsius) — returns True if celsius <= 0
4. to_fahrenheit(self) — returns the Fahrenheit value: self.celsius * 9 / 5 + 32
Create a temperature using Temperature.from_fahrenheit(32). Print:
0.0)to_fahrenheit() (should be 32.0)Temperature.is_freezing(0.0) (should be True)