Instance, Class, and Static Methods
Overview
Python classes support three distinct types of methods based on what state they need to access. The key mental model is to look at the first parameter to determine the method's scope. - Instance methods: The default method type. They operate on specific object instances and take `self` as their first argument. - Class methods: Decorated with `@classmethod`. They operate on the class itself by taking `cls` as their first argument. They return a new instance when used as alternative constructors. - Static methods: Decorated with `@staticmethod`. They act like regular functions that take neither `self` nor `cls`. They live inside a class's namespace purely for organizational purposes. A common edge case involves modifying class state through an instance method. class Counter: count = 0 def increment(self): # Trap: Creates an instance attribute, shadowing the class attribute! self.count += 1 Writing `self.count += 1` reads the class attribute `count`, adds `1`, and creates a new instance attribute `count` instead of updating `Counter.count` (covered fully in class-attributes). To modify class state safely, use a `@classmethod` and update `cls.count`. Think of `self` as a single employee, `cls` as the department manager, and `@staticmethod` as the office calculator.
They allow you to logically group related behaviors. Instead of having a bunch of disconnected functions, you can bundle utility functions (`@staticmethod`) and alternative constructors (`@classmethod`) 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 the 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?
- Why is it dangerous to update a class attribute using `self.attribute += 1` inside 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: Defining functions and return values
Previous: Function Decorators