AlgoMaster Logo

Class Methods (@classmethod)

High Priority13 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A class method is bound to the class itself, not to a particular instance. Where an instance method takes self and operates on one object's data, a class method takes cls and operates on the class as a whole. This shape supports two patterns that are awkward without it: alternative constructors that build instances from non-standard inputs, and class-level operations that don't need (or shouldn't require) an instance to exist first.

What @classmethod Does

The @classmethod decorator changes how Python wires up the first argument of a method. Instead of passing the instance, Python passes the class. By convention, the first parameter is named cls to make this clear.

First, default_discount was called both on the class (Product.default_discount()) and on an instance (mouse.default_discount()), and both calls worked. That's the rule for class methods: they're callable through either, and either form sends the class in as cls. Second, the body uses cls.discount_rate instead of Product.discount_rate. Using cls rather than hard-coding the class name matters once inheritance enters the picture.

@classmethod is a built-in function that wraps your method in a descriptor; that descriptor changes the lookup so the class gets passed in instead of the instance. You can write it without the decorator syntax as default_discount = classmethod(default_discount), and it would work the same way. The decorator is the prettier form.

Naming convention: Use cls for the first parameter of a class method, the same way you use self for an instance method. Python doesn't enforce it, but the convention is universal, and using anything else (klass, class_, c) makes your code look strange.

Class Method vs Instance Method

A clear way to see what @classmethod does is to put it side by side with a regular method.

discounted_price reaches into self.price, which is per-instance data. You can't call it without an instance because there's no price otherwise. Try Product.discounted_price() and Python complains that self is missing.

default_discount doesn't need any instance data; it only reads cls.discount_rate, which lives on the class. Calling it without an instance is reasonable, and Python doesn't make you create a throwaway Product just to ask "what's the standard discount?".

A practical test for which type of method to write: if the body needs self.something, it should be an instance method. If the body only reaches into cls.something (or doesn't touch state at all), a class method fits. A method that ignores both is a static method.

The mechanism Python uses to wire them up is similar. Both decorators install a descriptor on the class that produces the right kind of bound callable when accessed:

The diagram captures the key difference. Reading mouse.discounted_price always produces a bound method whose self is mouse. Reading default_discount (through either mouse or Product) produces a bound class method whose cls is Product. The class method ignores the instance even when accessed through one.

Alternative Constructors

A common reason to write a class method is to provide an alternative way to build an instance. The standard __init__ defines one signature. Sometimes you want callers to be able to create the same kind of object from different inputs: a string, a dictionary, a row from a database, a JSON payload. Class methods are the Pythonic way to expose those without overloading __init__ with optional parameters and conditional logic.

The pattern:

Three callers, three different inputs, one consistent end state. Each alternative constructor parses its specific format and then calls cls(...) to delegate to the regular __init__. The class method doesn't duplicate the assignment logic; it figures out the arguments and hands off.

Three things make this pattern work well:

  1. `cls(...)` instead of `Product(...)`. Using cls means the constructor still produces the right subclass when called from a subclass. Hard-coding Product would defeat that.
  2. Each constructor handles one input shape. Don't write a from_anything method that takes a dict | str | tuple and inspects the type. That's the design problem alternative constructors exist to avoid. Write from_dict, from_string, from_csv. Callers pick the one that matches their data.
  3. The naming follows a convention. Python's standard library uses from_ for alternative constructors (datetime.from_timestamp, dict.fromkeys). Following that convention makes intent clear without comments.

An example from the standard library is datetime:

All three produce a datetime instance. Each one fits a different way callers actually have the data. That's the idea behind alternative constructors: provide named entry points for the input shapes you care about.

Class-Level Operations and Factories

Not every class method builds an instance. Some operate on the class as a whole: counting how many instances exist, returning a registry, computing a value derived only from class attributes, or applying changes class-wide.

The count method returns information about the class as a whole, not about any single order. It would be strange to write some_order.count() and have it return the count of all orders; the class is the natural caller. The reset method clears the registry, which is similarly a class-wide operation.

This pattern shows up in factories, registries, and any case where the class itself holds state that multiple instances share. The class methods give you a clean place to expose operations on that shared state without exposing the underlying attributes directly.

Class-level mutable state like Order.all_orders = [] is shared by every test, every request, every code path in the program. It's easy to write, but it makes testing harder and creates coupling across code paths. Use it when you genuinely have one shared registry; use an explicit container (a Registry object) when you want more control.

A factory is a small step from an alternative constructor: a class method that returns an already-configured instance for a common case.

Both empty and starter_pack could have been calls to Cart(...) with the right arguments. The class methods exist because they name the intent. Cart.starter_pack("Bob") reads better than Cart("Bob", ["Welcome Gift Card", "Free Sticker"]) at a glance, especially when the items list is non-trivial.

How Inheritance Changes cls

The reason class methods use cls instead of the literal class name shows up when a class is subclassed. cls is whatever class the method was called on. If a subclass inherits a class method and a caller invokes it through the subclass, cls is the subclass, not the parent.

The same from_dict method, defined once on Product, produces a Product when called on Product and a DigitalProduct when called on DigitalProduct. That's the payoff for writing cls(...). If the method had written Product(...) directly, line b would have produced a Product, not a DigitalProduct, and the is_digital attribute would be missing.

This is one of the strongest reasons to choose @classmethod over a module-level function. A plain function would have to hard-code Product(...) or accept the class as a parameter. The class method uses cls, and subclasses inherit the correct behavior automatically.

A diagram of the dispatch:

The cyan and orange paths share the same method body but produce different result types because cls is bound differently for each caller.

Calling Class Methods on Instances

Class methods can be called through an instance. The instance is ignored; cls is still set to the class.

The call mouse.from_dict(...) works because Python looks up from_dict via the instance, finds the class method, and calls it with cls set to the class. The instance mouse is not passed in; it doesn't appear in the call signature at all.

In practice, this isn't a feature to lean on. Calling alternative constructors through an instance reads strangely (mouse.from_dict(...) looks like "build a new mouse from this dict using the existing mouse somehow", which isn't what happens). Stick to calling class methods through the class. Knowing they can also be called through an instance is mostly useful when reading code that does it accidentally.

When to Use a Class Method

Three situations call for @classmethod:

  1. Alternative constructors. Building an instance from a non-standard input format. from_dict, from_json, from_string, from_csv. Always use cls(...) to delegate to __init__ and to support subclasses.
  2. Operations on class-level state. Counting instances, returning a class-wide registry, resetting shared state. The method operates on the class, not on any single object.
  3. Factories that produce pre-configured instances. Cart.starter_pack(customer), Order.test_fixture(). The class method names the intent of a particular common case.

Cases where a class method is the wrong choice:

  • The function needs per-instance data. That's an instance method.
  • The function doesn't need either the class or any instance. That's a static method or, more often, a module-level function.
  • You're choosing @classmethod so you can avoid making the user create an instance first, but the method actually does use per-instance data. The fix is to make the user provide that data, not to fake it through class state.

A useful question to ask while writing: "Could this method ever produce different output for different instances?" If yes, instance method. If the answer depends only on the class (or on no state at all), class method or static method.

Comparison With Module-Level Functions

A @classmethod could often be written as a plain function in the same module. The two designs aren't equivalent, and the choice between them is worth thinking through.

Both produce a Product from a dictionary. The differences:

Aspect@classmethodModule-level function
Subclass supportcls automatically refers to the subclass when called via oneHard-coded Product; subclasses don't inherit anything
DiscoverabilityProduct.from_dict lives next to ProductCaller has to know about product_from_dict separately
NamespaceLives on the class, no separate importLives in the module, needs a separate import
EncapsulationCan access non-public class state directlySame access (Python's "private" is convention)

For alternative constructors specifically, @classmethod wins on subclass support and discoverability. A subclass DigitalProduct automatically inherits from_dict and uses it correctly. With the function version, every subclass would need its own digital_product_from_dict, or callers would have to pass the class as an argument.

For utilities that don't really belong to the class (a function that operates on a list of products to produce some summary, for example), the module-level function is usually cleaner. The general rule: if the function's job is to produce or operate on instances of the class, lean toward @classmethod; if its job is broader, prefer a plain function.

A Small Worked Example: An Order Builder

Pulling the patterns together, an Order class with __init__, two alternative constructors, and one class-level operation.

Three different call sites build orders three different ways, and all of them go through __init__ eventually. from_dict parses a dictionary; express is a factory that produces an already-shipped order for a single item; reset_ids is a class-wide operation that resets the auto-incrementing ID counter. All three are natural fits for @classmethod because they either build instances (via cls(...)) or work on class-level state (cls.next_id).

The same code without @classmethod would either require Order.next_id += 1 in three places (which is fragile) or use module-level functions that hard-code Order. The class methods keep the logic where it belongs.

Quiz

Class Methods (@classmethod) Quiz

10 quizzes