Beginner · Lesson 37 of 94

Class methods and static methods

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

Concept

Every method you have written so far is an instance method: it receives the object as self and works on that object's data. Two other kinds exist. A class method receives the class itself, conventionally named cls, and a static method receives nothing special at all. The decorator above the def tells Python which kind you mean: @classmethod or @staticmethod. Choosing between them comes down to one question: what does this method need to know about?

Kind Decorator First parameter Needs access to Typical job
instance method none self this object's data deposit, withdraw, describe
class method @classmethod cls the class, not an instance alternative constructors, registries
static method @staticmethod none neither a helper that belongs nearby

The most common use of a class method is an alternative constructor: a second way to build an instance. __init__ takes the raw pieces; Date.from_string("2026-09-08") or Account.from_dict(data) parses some other shape and then calls cls(...) with the pieces. Using cls rather than the class name means subclasses get the constructor for free and it builds the subclass, not the parent. Class methods also suit anything that reads or changes class-level state, like a counter of instances or a registry of known types.

Class methods and static methods: Account('ada', 100) → __init__: raw pieces → Account.from_dict({...}) → @classmethod: parse, then cls(owner, balance) → Account.from_csv_row('ada,100') → an Account instance
Class methods and static methods · Figure 1 · Open for full size

A static method is a plain function that lives inside the class because it belongs with it conceptually: a validation rule, a unit conversion, a formatting helper. It cannot see self or cls, which is the point: it declares "I depend on nothing but my arguments". You could put the same function at module level; making it static keeps it next to the class it serves and lets you call it as Account.is_valid_iban(text).

Class methods and static methods: instance method (self) → @classmethod (cls) → @staticmethod
Class methods and static methods · Figure 2 · Open for full size

Call any of the three through an instance or the class; the decorator decides what arrives. acct.from_dict(d) works but reads oddly; convention calls class and static methods on the class, Account.from_dict(d), so the reader knows no instance is involved.

Code

The three kinds side by side. Watch what each one receives when called on the class and on an instance.

# three_kinds.py
class Demo:
    def instance(self):
        return f"instance method got {type(self).__name__} object"

    @classmethod
    def klass(cls):
        return f"class method got the class {cls.__name__}"

    @staticmethod
    def static():
        return "static method got nothing special"


d = Demo()
print(d.instance())
print(d.klass())
print(Demo.klass())
print(Demo.static(), "|", d.static())
Output
instance method got Demo object
class method got the class Demo
class method got the class Demo
static method got nothing special | static method got nothing special

Alternative constructors. Both class methods parse a different input shape and then call cls(...), so the real construction logic stays in one place.

# constructors.py
class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def __repr__(self):
        return f"Account({self.owner!r}, {self.balance})"

    @classmethod
    def from_dict(cls, data):
        return cls(data["owner"], data.get("balance", 0))

    @classmethod
    def from_csv_row(cls, row):
        owner, balance = row.split(",")
        return cls(owner.strip(), float(balance))


print(Account("ada", 100))
print(Account.from_dict({"owner": "linus"}))
print(Account.from_csv_row("grace, 250.5"))
Output
Account('ada', 100)
Account('linus', 0)
Account('grace', 250.5)

Why cls and not the class name: a subclass calling the inherited constructor gets an instance of itself.

# cls_matters.py
class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    @classmethod
    def from_dict(cls, data):
        return cls(data["owner"], data["balance"])


class SavingsAccount(Account):
    rate = 0.03


s = SavingsAccount.from_dict({"owner": "ada", "balance": 500})
print(type(s).__name__, s.rate, s.balance)
Output
SavingsAccount 0.03 500

Static methods as helpers, and a class method reading class-level state. The validator depends only on its argument; the counter depends on the class.

# helpers.py
class Account:
    created = 0

    def __init__(self, owner, iban):
        if not self.is_valid_iban(iban):
            raise ValueError(f"bad IBAN: {iban}")
        self.owner = owner
        self.iban = iban
        Account.created += 1

    @staticmethod
    def is_valid_iban(text):
        cleaned = text.replace(" ", "")
        return cleaned[:2].isalpha() and cleaned[2:].isalnum() and 15 <= len(cleaned) <= 34

    @classmethod
    def how_many(cls):
        return f"{cls.created} {cls.__name__} objects created"


print(Account.is_valid_iban("GB82 WEST 1234 5698 7654 32"))
print(Account.is_valid_iban("12345"))
Account("ada", "GB82 WEST 1234 5698 7654 32")
Account("linus", "DE89 3704 0044 0532 0130 00")
print(Account.how_many())
Output
True
False
2 Account objects created

Try it yourself

Create temperature.py with a Temperature class storing degrees Celsius. Add a class method from_fahrenheit(f) and another from_kelvin(k), a static method is_plausible(celsius) that returns whether the value is above absolute zero, and an instance method describe(). Build one temperature each way, print the descriptions, and call the validator on the class with a valid and an impossible value.

Common mistakes

What's next

You now know every kind of method a class can have. Next you will build classes on top of other classes: inheritance and super().

Course overview