Type something to search...
Class Methods vs Static Methods vs Instance Methods in Python

Class Methods vs Static Methods vs Instance Methods in Python

Python classes can have three kinds of methods. Regular instance methods are the ones you write all the time. Then there are methods decorated with @classmethod and @staticmethod, which show up in library code and interview questions and tend to get explained with abstract examples that don't make it clear when you'd actually choose one over another.

The difference comes down to one question: what does the method receive as its first argument? An instance method gets the object, a class method gets the class, and a static method gets nothing extra. Everything else, including when to use each, follows from that.

This post shows how the three behave side by side, then looks at the real use cases: alternative constructors with @classmethod, helpers with @staticmethod, class-level state, and how each behaves under inheritance. If you need a refresher on self and __init__, see classes and objects in Python.

The Three Kinds Side by Side

class Demo:
    def instance_method(self) -> str:
        return f"instance method called with {self!r}"

    @classmethod
    def class_method(cls) -> str:
        return f"class method called with {cls!r}"

    @staticmethod
    def static_method() -> str:
        return "static method called with nothing"

    def __repr__(self) -> str:
        return "<Demo instance>"


d = Demo()
print(d.instance_method())
print(d.class_method())
print(d.static_method())
print(Demo.class_method())
print(Demo.static_method())
instance method called with <Demo instance>
class method called with <class '__main__.Demo'>
static method called with nothing
class method called with <class '__main__.Demo'>
static method called with nothing
  • Instance method (no decorator): the first parameter, self, is the instance it was called on. It can read and change that object's data, and reach the class through type(self).
  • Class method (@classmethod): the first parameter, cls by convention, is the class. It's the same whether you call it on the class (Demo.class_method()) or an instance (d.class_method()). It can't see any particular instance's data, but it can read class attributes and create new instances.
  • Static method (@staticmethod): no automatic first argument at all. It's a plain function that happens to live in the class's namespace.

Calling an instance method on the class, without an instance, fails because there's nothing to fill self with:

Demo.instance_method()
TypeError: Demo.instance_method() missing 1 required positional argument: 'self'

You can pass the instance explicitly, Demo.instance_method(d), which shows that d.instance_method() is really just shorthand for that.

What Python Does with Each

When you access a method through an instance, Python binds it, attaching the first argument for you. Printing the methods shows the difference:

print(d.instance_method)
print(Demo.class_method)
print(d.static_method)
<bound method Demo.instance_method of <Demo instance>>
<bound method Demo.class_method of <class '__main__.Demo'>>
<function Demo.static_method at 0x1096bc4a0>

The instance method is bound to d. The class method is bound to the class, even when accessed through an instance. The static method isn't bound to anything; it's handed back as a plain function. The decorators work by changing how this binding happens (they're descriptors, the same mechanism behind @property).

Quick Comparison

Instance methodClass methodStatic method
DecoratorNone@classmethod@staticmethod
First argumentThe instance (self)The class (cls)None
Read/modify instance stateYesNoNo
Read/modify class stateYes, via type(self)YesOnly by naming the class
Callable on the classOnly with an instance passedYesYes
Respects subclassesYesYes, cls is the subclassNo
Typical useMost behaviorAlternative constructors, class-wide stateHelpers related to the class

Instance Methods: The Default

Most methods should be instance methods. If a method uses self to read or change the object's data, that's what it is, and there's nothing more to decide. The interesting question is the reverse: when a method doesn't use self, should it be a class method, a static method, or not a method at all?

Class Methods: Alternative Constructors

The most common and most useful reason to write a class method is to provide other ways of creating an object. A class has one __init__, but objects often come from different sources: a string, a dictionary, a file, a database row.

# events.py
import re
from datetime import date
from typing import Any, Self


class Event:
    _DATE_PATTERN = re.compile(r"^(\d{4})-(\d{2})-(\d{2})$")

    def __init__(self, title: str, day: date) -> None:
        self.title = title
        self.day = day

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.title!r}, {self.day.isoformat()})"

    # Instance method: works with one event's data.
    def days_until(self, today: date) -> int:
        return (self.day - today).days

    # Class methods: alternative constructors.
    @classmethod
    def from_string(cls, text: str) -> Self:
        title, _, raw_date = text.rpartition("@")
        return cls(title.strip(), cls.parse_date(raw_date.strip()))

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Self:
        return cls(data["title"], date.fromisoformat(data["day"]))

    # Static method: a helper that needs neither the class nor an instance.
    @staticmethod
    def parse_date(text: str) -> date:
        match = Event._DATE_PATTERN.match(text)
        if not match:
            raise ValueError(f"expected YYYY-MM-DD, got {text!r}")
        return date(*map(int, match.groups()))


class Meeting(Event):
    pass


launch = Event.from_string("Product launch @ 2026-11-03")
standup = Meeting.from_dict({"title": "Standup", "day": "2026-09-22"})
print(launch, standup)
print(launch.days_until(date(2026, 9, 21)))
print(Event.parse_date("2026-12-25"))
Event('Product launch', 2026-11-03) Meeting('Standup', 2026-09-22)
43
2026-12-25

The class has all three kinds of method, each for a reason:

  • days_until needs a specific event's date, so it's an instance method.
  • from_string and from_dict create events from other formats. They call cls(...), not Event(...), which is the key detail. When called as Meeting.from_dict(...), cls is Meeting, so you get a Meeting back without Meeting having to redefine anything. The Self return type (from typing, Python 3.11+) tells type checkers the same thing.
  • parse_date turns a string into a date. It doesn't need the class or an instance; it's a self-contained helper that belongs with Event because that's the only place it's used.

The naming convention from_<source> is worth following. The standard library uses it all over the place: dict.fromkeys(), datetime.fromisoformat(), datetime.fromtimestamp(), int.from_bytes(), and date.today() are all class methods that act as alternative constructors.

Alternative constructors keep __init__ simple. Instead of one initializer with a tangle of optional parameters for every input format, __init__ takes the canonical values, and each class method handles one way of producing them. If parsing fails, the error happens before an object exists.

Why cls Beats a Hardcoded Class Name

You might wonder why from_dict isn't a static method that returns Event(...). Here's what happens when you try that with inheritance:

class Pizza:
    def __init__(self, toppings: list[str]) -> None:
        self.toppings = toppings

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.toppings})"

    @staticmethod
    def margherita_static() -> "Pizza":
        return Pizza(["tomato", "mozzarella"])

    @classmethod
    def margherita(cls) -> "Pizza":
        return cls(["tomato", "mozzarella"])


class GlutenFreePizza(Pizza):
    pass


print(GlutenFreePizza.margherita_static())
print(GlutenFreePizza.margherita())
Pizza(['tomato', 'mozzarella'])
GlutenFreePizza(['tomato', 'mozzarella'])

The static version always builds a Pizza, even when you ask the subclass for one, which is a subtle bug. The class method builds whatever class it was called on. Any method that creates instances of its own class should be a class method.

Class Methods for Class-Level State

The second use for class methods is working with data that belongs to the class as a whole rather than any one instance: counters, registries, shared configuration.

class Connection:
    _open_count = 0
    max_connections = 2

    def __init__(self, host: str) -> None:
        type(self)._register()
        self.host = host

    @classmethod
    def _register(cls) -> None:
        if cls._open_count >= cls.max_connections:
            raise RuntimeError(f"{cls.__name__}: too many open connections")
        cls._open_count += 1

    @classmethod
    def open_count(cls) -> int:
        return cls._open_count


a = Connection("db1")
b = Connection("db2")
print(Connection.open_count())
Connection("db3")
2
RuntimeError: Connection: too many open connections

Writing cls._open_count += 1 updates the attribute on the class. Doing the same thing through self in an instance method is a classic bug:

class Counter:
    count = 0

    def bump_wrong(self) -> None:
        self.count += 1  # creates an instance attribute, class untouched

    @classmethod
    def bump(cls) -> None:
        cls.count += 1


c = Counter()
c.bump_wrong()
print(Counter.count, c.count)
Counter.bump()
print(Counter.count, Counter().count)
0 1
1 1

self.count += 1 reads the class attribute but writes a new instance attribute on c, leaving the class's counter at zero. The class method updates the shared value.

One thing to keep in mind: with inheritance, cls._open_count += 1 called on a subclass creates a separate counter on that subclass. Sometimes that's what you want (per-subclass counts); if it isn't, name the base class explicitly: Connection._open_count += 1.

Static Methods: Helpers in the Class Namespace

A static method is just a function. It receives no special arguments and can't affect the class or instances unless you pass them in. So why put it in the class at all?

  • Grouping. Event.parse_date is clearly tied to events, and anyone reading the class sees it there.
  • Overridability. Because it's looked up through the class (self.parse_date(...) or cls.parse_date(...)), a subclass can override it with a different implementation.
  • Discoverability. It shows up in IDE autocomplete on the class and in help(Event).

That said, Python has modules, and a module-level function is often the better home. Ask: does anything other than this class need this function? If so, move it out. A parse_date that's useful across the codebase belongs in a dates.py module, not on Event. The Python community leans toward module-level functions more than languages like Java, where every function must live in a class.

A useful smell test: a class with only static methods should be a module. class StringUtils with ten static methods is a Java-ism. In Python, make it string_utils.py with ten functions. See what are Python modules and packages for how to organize that.

Also, note that parse_date refers to Event._DATE_PATTERN by name. That's the main limitation of static methods: when they need class data, they have to hard-code the class. If you find yourself doing that a lot, it probably wants to be a class method instead.

How to Choose

Work through these questions in order:

  1. Does the method need data from a specific instance? Use an instance method. (This is most methods.)
  2. Does it create an instance of the class, or work with class-level data? Use a class method, and use cls rather than the class name.
  3. Is it a helper that's specific to this class but uses neither? A static method is fine.
  4. Is it a general utility that other code could use too? Make it a module-level function.

Some quick examples:

MethodKind
order.add_item(product)Instance
order.total()Instance
Order.from_json(text)Class method
Order.from_row(db_row)Class method
Plugin.registered() (list subclasses)Class method
Order.validate_sku(sku)Static method
slugify(text) used everywhereModule-level function

Inheritance and Overriding

All three kinds can be overridden in subclasses, and calls through self or cls pick up the override:

  • Instance methods dispatch on type(self), as you'd expect.
  • Class methods receive the subclass as cls, so overridden class attributes and constructors are used. Inside a class method you can call super().from_dict(data) to extend the parent's version, and cls is still the subclass.
  • Static methods can be overridden too, but only calls that go through self. or cls. see the override. A call that names the class directly, like Event.parse_date(...), always gets the base version.

For more on how super() finds the next method, see inheritance and super() in Python.

A Few Details

  • Names are conventions. self and cls aren't keywords, but every Python developer expects them. Don't rename them.
  • Decorator order matters when stacking. @classmethod and @staticmethod should usually be the outermost (top) decorator. Chaining @classmethod with @property to make a class-level property was deprecated in Python 3.11 and no longer works in 3.13.
  • Static methods are callable directly. Since Python 3.10, a staticmethod object is itself callable, so you can call it from within the class body during class creation. Rarely needed, but it removes an old surprise.
  • Abstract methods combine with both. You can stack @classmethod or @staticmethod on top of @abstractmethod to require subclasses to implement them; see abstract base classes.

Conclusion

The three kinds of method differ only in what they receive first: instance methods get the object, class methods get the class, and static methods get nothing. Use instance methods for almost everything. Use class methods for alternative constructors named from_<something> and for class-wide state, and always build instances with cls(...) so subclasses work. Use static methods sparingly, for helpers that clearly belong to the class, and move anything more general out to a module-level function. Once you think in terms of "what does this method need?", the right decorator is usually obvious.

Tags :
Share :

Related Posts

Abstract Base Classes in Python with the abc Module

Abstract Base Classes in Python with the abc Module

Python leans on duck typing: if an object has the method you need, you call it and move on. That works well until you have a family of classes that a

Continue Reading
*args and **kwargs in Python: Flexible Function Signatures

*args and **kwargs in Python: Flexible Function Signatures

You've seen def wrapper(*args, **kwargs): in decorators, and probably super().__init__(**kwargs) in class hierarchies. These two parameters let a

Continue Reading
Asyncio in Python: A Beginner's Guide to Asynchronous Programming

Asyncio in Python: A Beginner's Guide to Asynchronous Programming

A lot of programs spend most of their time waiting. A web scraper waits for pages to download, an API server waits for the database, a chat bot waits

Continue Reading