Class methods, static methods & class attributes
Build alternative constructors with @classmethod, and know which state every instance shares.
Three kinds of method, three kinds of first argument
Every function in a class body is one of three things, and the decorator on it decides what Python passes as the first argument.
| Written as | First argument | Reads or writes |
|---|---|---|
def m(self) | the instance | that one object's data |
@classmethod def m(cls) | the class | data shared by every instance |
@staticmethod def m() | nothing | only its own arguments |
class User:
role = "member" # class attribute: one copy, shared
def __init__(self, name):
self.name = name # instance attribute: one per object
def greet(self): # instance method
return f"{self.name} ({self.role})"
@classmethod
def from_csv(cls, line): # alternative constructor
return cls(line.split(",")[0])
@staticmethod
def is_valid(name): # plain helper, namespaced on the class
return len(name) > 0
print(User.from_csv("ada,42").greet()) # ada (member)
print(User.is_valid("")) # False
Why @classmethod is the alternative constructor
__init__ can only have one signature. Real data arrives in several shapes: a CSV line, a dict from JSON, a database row. A class method builds the object from each shape and hands the work to the single __init__.
The first argument is cls, not the literal class name, and that difference matters. cls(...) builds whatever class the call was made on, so a subclass inherits the constructor for free:
class Admin(User):
role = "admin"
print(type(Admin.from_csv("grace,7"))) # <class '__main__.Admin'>
Hard-coding User(...) inside from_csv would have returned a User even when called on Admin, silently discarding the subclass.
The class attribute that everyone shares
A class attribute lives on the class, so every instance sees the same object. Read that as a shared default and it is useful. Mutate it and every instance changes at once.
▸ class Cart: items = []a = Cart() b = Cart()a.items.append("apple")
- Cart.itemsL1
- L1list[]
One list object, created once when the class body runs.
The fix is to create the list per instance, inside __init__:
class Cart:
def __init__(self):
self.items = [] # a fresh list for every Cart
Pitfalls
- Mutable class attributes.
items = []in the class body makes one list for the whole program. Use it for genuine constants (role = "member",MAX_RETRIES = 3) and build mutable state in__init__. - Assignment does not mutate.
a.items = ["x"]creates a new instance attribute that shadows the class one, sob.itemsis unaffected. Only mutation (append,+=on a list) leaks across instances, which is why the bug is so easy to miss. - Hard-coding the class inside a classmethod. Return
cls(...), neverUser(...), or subclasses get the wrong type back. - Reaching for @staticmethod too often. If it touches neither
selfnorcls, ask whether it wants to be a module-level function. Keep it a static method only when the class is the natural place a reader would look for it.
Interview nuance: "why is cls better than the class name" tests whether you understand that Python resolves attributes at call time through the instance's own class. cls is polymorphic; the literal name is not. The same reasoning explains why super().__init__() beats ParentClass.__init__(self): both keep the inheritance chain intact instead of pinning one link in it.
class Cart:
items = [] # shared by every instance
a = Cart()
b = Cart()
a.items.append("apple")
print(b.items) # ['apple'] - one list, not twoApply
Your turn
The task this lesson builds to.
Write a @classmethod named from_csv that builds a User from a "name,age" string and returns the user's name.
run("ada,36") should return "ada". Build the object with cls, not User.
3 hints and 4 automated checks are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Fix Cart so each cart has its own item list, then return the number of items in the second cart.
run("apple") should return 0: adding to the first cart must not touch the second.
3 hints and 3 automated checks are waiting in the workspace.