instance / class / static methods
Overview
Python classes support three types of methods. Instance methods (the default) take `self` as the first argument and operate on a specific object instance. Class methods use the `@classmethod` decorator, take `cls` as their first argument, and operate on the class itself (often used for alternative constructors). Static methods use the `@staticmethod` decorator, take neither `self` nor `cls`, and act like regular functions that just happen to live inside a class's namespace for organizational purposes. The key mental model: look at the first parameter. If it needs the specific object, use `self`. If it needs the class (to create instances or read class-level data), use `cls`. If it needs neither, use `@staticmethod`.
They allow you to logically group related behaviors. Instead of having a bunch of disconnected functions, you can bundle utility functions (static) and alternative constructors (class methods) directly onto the class they relate to.
Where used: Alternative constructors like `dict.fromkeys()`, Utility functions attached to models in Pydantic
Why learn this
- Building clean alternative constructors (e.g., `User.from_json()`)
- Organizing utility functions inside related classes instead of polluting module namespace
Code walkthrough
class Config:
prefix = 'app_'
@classmethod
def get_key(cls, name):
return cls.prefix + name
print(Config.get_key('port'))
Focus: return cls.prefix + name
Aha moment
class Parent:
@classmethod
def create(cls):
return cls()
class Child(Parent):
pass
print(type(Child.create()).__name__)
Prediction: What does this print? (Parent or Child?)
Common guess: Parent
Because it's a `@classmethod`, `cls` becomes the class it was called on (`Child`), not the class where it was defined (`Parent`). This makes class methods perfect for inheritable constructors.
Common mistakes
- Forgetting the decorator: If you forget `@classmethod`, the method is just a plain instance method. Calling it on the class raises a TypeError because there's no instance to fill `self`, and calling it on an instance passes the instance instead of the class.
- Using staticmethod when classmethod is needed: Using `@staticmethod` to build a constructor means you have to hardcode the class name inside the method. If the class is subclassed, the static method will still create instances of the parent class, whereas `@classmethod` dynamically uses the subclass `cls`.
Glossary
- constructors
- Special methods used to create and initialize a new instance of a class, e.g. `def __init__(self, name): self.name = name`.
- namespace
- A conceptual space or container that holds a set of names, ensuring that all names are unique and don't conflict with each other, e.g. `Config.clean_key` lives in the `Config` namespace.
Recall questions
- What is the primary use case for a class method?
- How does a static method differ from an instance method?
Understanding checks
What does this code output?
2
The `@classmethod` takes `cls` (the class itself) as the first argument. Modifying `cls.count` updates the class-level attribute `count` directly. Calling it twice increments the class attribute to 2.
A developer writes a utility function inside a class that doesn't use `self` or `cls`, but they forget to add the `@staticmethod` decorator. Why will this fail when called from an instance?
Without `@staticmethod`, Python assumes it is an instance method and automatically passes the instance (`self`) as the first argument, causing a TypeError for receiving too many arguments.
Python's default behavior for methods inside a class is to pass the instance as the first argument. `@staticmethod` explicitly disables this behavior.
Practice tasks
Alternative Constructor
Modify the `User` class to include a class method named `from_csv_string`. It should take a `csv_string` (e.g., 'alice,alice@example.com'), split it by the comma, and return a new instance of the class.
Challenge
Config Parser
Write a `ConfigParser` class. It should have a static method `clean_key(key)` that strips whitespace and lowercases a string. It should also have a class method `from_dict(data)` that takes a dictionary, cleans all its keys using `clean_key`, and returns a new `ConfigParser` instance where the cleaned data is stored in `self.config`. Its `__init__` should just take and store the config dictionary.
Continue learning
Previous: Decorators