Python: Instance Methods, Class Methods, Static Methods

Python class methods, static methods, and plain instance methods look almost identical, which is why your cursor freezes the moment you start writing a method inside a class. Should it be a plain method, a @classmethod, or a @staticmethod? Almost every Python developer guesses wrong at least once. The good news: there is a 10 second rule that picks the right one every time.

“Choosing between instance, class, and static methods is choosing who needs to know what.”

Raymond Hettinger, PyCon talks

Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Intermediate | Reading Time: 14 minutes

Think of a class like a pizza shop. An instance method works on one specific order (“add cheese to this pizza”). A class method works on the shop itself (“here is our standard margherita recipe”). A static method is a handy tool kept on the counter that anyone can grab (“is this a valid pizza size?”), it does not care about any single order or the shop. Same kitchen, three different jobs.

The only thing that really separates them is the first argument. Instance methods get self (the object). Class methods get cls (the class). Static methods get neither. That single difference decides what data the method can touch, and that is exactly what you use to choose. The rest of this post hands you a flowchart, a comparison table, and three real examples so the choice becomes automatic.

The 10 Second Decision Flowchart

Before any code, here is the whole decision in one picture. Start at the top with your Python class and ask one question: what does this method need to touch? Follow the branch and you land on the right method type. Screenshot this and you will never guess again.

🏗️ Python ClassInstance Methoddef method(self)Class Method@classmethoddef method(cls)Static Method@staticmethoddef method() Access instancedata via self Access class datavia self.__class__📌 Most commonmethod type Access classdata via cls Cannot accessinstance data📌 Factory methods andalt constructors No access toself or cls Utility/helperfunctions📌 Logically belongsto classPython Methods: Instance vs Class vs Static, What Each Can Access

Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.

Read the diagram top to bottom. The pink box is your class. Below it sit the three method types, color coded. Under each one, the gray boxes tell you what it can and cannot reach. Instance methods (green) get self and can touch the object’s own data. Class methods (purple) get cls and can reach class level data and build new objects. Static methods (orange) get nothing, so they are plain helper functions that just happen to live in the class. What each one can reach is the decision rule. The table below turns that picture into a quick lookup.

The Three Method Types at a Glance

This is the table to bookmark for Python class methods, static methods, and instance methods side by side. Every row is a real difference you will hit in actual code, not marketing fluff.

FeatureInstance MethodClass MethodStatic Method
DecoratorNone@classmethod@staticmethod
First argself (instance)cls (class)Nothing
Access instance?YesNoNo
Access class?Yes (via self.__class__)YesNo
Call via instance?YesYesYes
Call via class?No (need instance)YesYes
Use caseOperates on instance dataFactory methods, class stateUtility/helper functions

Instance Methods: The Default

This is the one you already know. Any method whose first parameter is self is an instance method, and roughly nine out of ten methods you ever write will be this kind. The self is the specific object you called the method on, so the method can read and change that object’s own data. In the example below, a shopper named Anvi fills her cart, and every method call works on her cart alone.

📄 instance_methods.py: methods that work with self

class ShoppingCart:
    def __init__(self, owner):
        self.owner = owner
        self.items = []

    def add_item(self, name, price):
        self.items.append({"name": name, "price": price})
        return self  # Enable method chaining

    def total(self):
        return sum(item["price"] for item in self.items)

    def summary(self):
        return f"{self.owner}'s cart: {len(self.items)} items, Rs.{self.total()}"

cart = ShoppingCart("Anvi")
cart.add_item("Python Book", 699).add_item("USB Cable", 299).add_item("Coffee", 150)
print(cart.summary())

▶ Output

Anvi's cart: 3 items, Rs.1148

What happened here: When you call cart.summary(), Python quietly passes cart in as self. You never type that argument yourself. Inside the method, self.items and self.owner are this cart’s own data, so the method always works on the right object. One neat trick is in add_item: it ends with return self, which hands the same cart back. That lets you chain calls in a single line, like cart.add_item(...).add_item(...).add_item(...), since each call returns the cart again. The shop analogy holds: an instance method is a worker handling this one order.

Class Methods: The Factory

Sometimes you want to build an object in more than one way. Maybe from a JSON (JavaScript Object Notation) string, maybe from a preset, maybe from a database row. You could write one giant __init__ that handles every case, but that gets ugly fast. A class method is the clean answer. It receives the class itself as cls, so it can call cls(...) to stamp out fresh objects. Think of it as the shop’s recipe card: “to make our standard config, mix these defaults.”

📄 class_methods.py: methods that work with cls

import json

class Config:
    def __init__(self, db_host, db_port, debug):
        self.db_host = db_host
        self.db_port = db_port
        self.debug = debug

    @classmethod
    def from_json(cls, json_string):
        data = json.loads(json_string)
        return cls(data["db_host"], data["db_port"], data["debug"])

    @classmethod
    def development(cls):
        return cls("localhost", 5432, True)

    @classmethod
    def production(cls):
        return cls("db.prod.internal", 5432, False)

    def __repr__(self):
        return f"Config({self.db_host}:{self.db_port}, debug={self.debug})"

dev = Config.development()
prod = Config.production()
from_data = Config.from_json('{"db_host": "staging.db", "db_port": 5433, "debug": true}')

print(dev)
print(prod)
print(from_data)

▶ Output

Config(localhost:5432, debug=True)
Config(db.prod.internal:5432, debug=False)
Config(staging.db:5433, debug=True)

What happened here: Each of these methods is decorated with @classmethod, so Python passes the class (Config) in as cls instead of an object. None of them has a self, so they cannot read instance data, and that is fine, there is no instance yet. Their whole job is to make one. Config.development() calls cls("localhost", 5432, True), which is just Config(...) with friendly defaults filled in. from_json does the same after parsing a JSON string. These alternative constructors are the number one reason class methods exist. Notice the JSON used true (lowercase) and Python correctly read it back as True.

Static Methods: The Utility

A static method is the odd one out. It gets no self and no cls. It is really just a normal function that you parked inside a class because it belongs there logically. Picture a calculator sitting on the shop counter: it does not know about any order or the shop, it just does its job when you press the buttons. Use this when a helper relates to the class by theme but does not need any object or class data to do its work.

📄 static_methods.py: methods that need no self or cls

class MathHelper:
    @staticmethod
    def is_even(n):
        return n % 2 == 0

    @staticmethod
    def celsius_to_fahrenheit(c):
        return c * 9/5 + 32

    @staticmethod
    def validate_email(email):
        return "@" in email and "." in email.split("@")[1]

# Called straight on the class, no instance needed
print(MathHelper.is_even(42))
print(MathHelper.celsius_to_fahrenheit(37))
print(MathHelper.validate_email("aviraj@technoscripts.com"))
print(MathHelper.validate_email("bad-email"))

▶ Output

True
98.6
True
False

What happened here: Notice none of these three methods has self or cls. They take only the data they need to do the calculation. celsius_to_fahrenheit(37) returns 98.6 straight from the formula, no object required. We grouped them under MathHelper so the name reads nicely (MathHelper.is_even(42)) and related helpers stay together. Here is the simple rule: if a method never uses self and never uses cls, it is begging to be a static method.

Here is the flowchart in words. When you are about to write a method, ask three questions in order and stop at the first “yes”:

  1. Does it use this object’s own data? Yes, then plain instance method (first arg self, no decorator).
  2. Does it build new objects or read class-level data? Yes, then @classmethod (first arg cls).
  3. Neither? Then @staticmethod, or honestly, ask yourself if it should just be a plain function in a module.

All Three in One Class

Theory sticks better when you see all three working together. Here is a tiny Pizza class that uses one of each: an instance method to price a specific pizza, two class methods that act as factories for popular combos, and a static method that validates a size without needing any pizza at all.

📄 all_three.py: a practical example using all three method types

class Pizza:
    sizes = {"small": 199, "medium": 349, "large": 499}

    def __init__(self, size, toppings):
        self.size = size
        self.toppings = toppings

    # Instance method: works with this specific pizza
    def price(self):
        base = Pizza.sizes[self.size]
        topping_cost = len(self.toppings) * 49
        return base + topping_cost

    # Class method: factory for common combos
    @classmethod
    def margherita(cls, size="medium"):
        return cls(size, ["mozzarella", "basil", "tomato sauce"])

    @classmethod
    def paneer_tikka(cls, size="medium"):
        return cls(size, ["mozzarella", "paneer tikka"])

    # Static method: utility that does not need self or cls
    @staticmethod
    def is_valid_size(size):
        return size in ("small", "medium", "large")

p1 = Pizza.margherita("large")
p2 = Pizza.paneer_tikka()

print(f"Margherita (L): Rs.{p1.price()}")
print(f"Paneer Tikka (M): Rs.{p2.price()}")
print(f"Is 'huge' valid? {Pizza.is_valid_size('huge')}")

▶ Output

Margherita (L): Rs.646
Paneer Tikka (M): Rs.447
Is 'huge' valid? False

What happened here: Watch how each method gets called. p1.price() runs on a specific pizza, so it needs self to know which toppings to count. A large margherita is the large base (499) plus three toppings at 49 each, so 499 + 147 = 646. Pizza.margherita("large") never touched an existing pizza; it built a brand new one through cls(...), which is the factory job. And Pizza.is_valid_size("huge") just answers a yes or no question with no pizza in sight, the perfect static method. One class, three jobs, three method types, exactly like the flowchart promised.

Real-World Scenarios

Forget pizzas for a second. Here is where Python class methods and their two siblings show up in code you will actually ship:

  • Loading a user from a database row calls for a class method. You write User.from_row(row) that parses the row and returns cls(...). The raw __init__ stays clean while the messy parsing lives in its own named constructor.
  • Updating an order’s status is an instance method. order.mark_shipped() changes self.status on that one order. It is about a single object, so self is exactly what you need.
  • Checking if a phone number looks valid is a static method. Customer.is_valid_phone("9876543210") needs no customer and no class data, just the string. It lives on Customer only because that is where you go looking for it.

Common Mistakes

Mistake 1: Using @staticmethod when you need @classmethod

📄 mistake_wrong_decorator.py

# Two versions of the SAME factory, shown side by side for contrast
class AnimalBad:
    # BAD: hardcodes the class name, so subclasses cannot reuse it
    @staticmethod
    def create_dog():
        return AnimalBad("Dog")

    def __init__(self, species):
        self.species = species

class AnimalGood:
    # GOOD: cls adapts automatically, even for subclasses
    @classmethod
    def create_dog(cls):
        return cls("Dog")

    def __init__(self, species):
        self.species = species

Think of a franchise recipe card. A card that says “prepare this at the Pune branch” is useless the day you open a branch in Nagpur. A card that says “prepare this at whichever branch you are standing in” works everywhere, and that is exactly what cls gives you. The @staticmethod version bakes in the name AnimalBad. If you later make a Puppy(AnimalBad) subclass and call Puppy.create_dog(), you still get an AnimalBad, not a Puppy, which is almost never what you want. The @classmethod version uses cls, so Puppy.create_dog() correctly returns a Puppy. Rule of thumb: if a method calls the constructor, it should be a class method.

Mistake 2: Making everything a static method

If your class is nothing but static methods, that is a smell. You probably do not need a class at all, just a module with plain functions. Classes earn their keep by bundling data with the behavior that acts on it. No data, no reason for a class.

Decision Summary

The whole post in five lines you can keep next to your keyboard:

  • Use an instance method when the work touches one object’s data. This is your default and most methods fit here.
  • Use @classmethod for factory methods and alternative constructors (anything that calls cls(...) to build an object).
  • Use @staticmethod for helper functions that belong with the class by theme but need no self or cls.
  • Never reach for @staticmethod when the method builds objects. That is a class method’s job, and static breaks with inheritance.
  • Stop and rethink if a class is all static methods. A module of plain functions is simpler and clearer.

Practice Exercises

  1. Build a Temperature class. Give it an instance method that returns the value in Fahrenheit, a class method from_fahrenheit(cls, f) that builds an object from a Fahrenheit reading, and a static method is_freezing(celsius) that returns True below zero. One class, all three method types.
  2. Spot the wrong decorator. Take a class where a factory method is marked @staticmethod and hardcodes the class name. Make a subclass, prove it returns the wrong type, then switch it to @classmethod with cls and prove it now adapts.
  3. Refactor a junk-drawer class. Find or write a class that is nothing but static methods. Decide which ones truly belong together, then move the rest into a plain module of functions. Notice how much simpler the code reads.

Conclusion

Python gives you three method types: instance methods for working with object data, class methods for factory patterns and class-level operations, and static methods for utilities that belong logically to the class. The whole choice comes down to one question, what does this method need to touch: the instance, the class, or neither? Answer that and the decorator picks itself.

With Python class methods, static methods, and instance methods sorted, you are ready for inheritance. In the inheritance tutorial, you will learn how to create class hierarchies where child classes inherit and extend behavior from parent classes. And if you want the full roadmap from Python basics all the way to AI and ML, browse the Python + AI/ML tutorial series home.

Frequently Asked Questions

What is the difference between @classmethod and @staticmethod in Python?

@classmethod receives the class as cls and can access class attributes and create instances. @staticmethod receives nothing, so it is just a regular function living inside a class. Use classmethod for factory patterns, staticmethod for utilities.

When should I use @classmethod in Python?

Use @classmethod when you need to: create instances from alternative data sources (factory methods like from_json, from_csv), create preset configurations (Config.development()), or access/modify class-level attributes.

Can I call a class method on an instance?

Yes. obj.class_method() works fine, and Python still passes the class, not the instance. But calling it on the class (MyClass.class_method()) is clearer and preferred.

When should I use @staticmethod vs a standalone function?

Use @staticmethod when the function is logically related to the class (like a validator or converter). Use a standalone function when it has nothing to do with the class. If you’re unsure, standalone function is usually the simpler choice.

What does self refer to in a Python method?

self refers to the specific instance the method was called on. When you call cart.total(), Python passes cart as self. That’s how the method knows which cart’s items to sum up.

Interview Questions on Python Class Methods

Scenario questions, not trivia: this is the form this topic takes in a real interview.

Q: A teammate adds a from_dict factory to a base Model class, but when your colleague Anvay calls User.from_dict(data) on his User(Model) subclass, he gets a Model back instead of a User. What went wrong?

The factory is almost certainly hardcoding the class name, either return Model(...) inside a @staticmethod or even inside a @classmethod that ignores cls. Because the class name is baked in, every subclass gets a Model. The fix is to make it a @classmethod that returns cls(...): when called as User.from_dict(data), Python passes User in as cls, so the right type comes out automatically.

Q: You forget the @classmethod decorator on def development(cls) in a Config class. What happens when someone calls Config.development()?

Without the decorator it is a plain instance method, so Python does not pass the class automatically. Calling Config.development() on the class raises TypeError: development() missing 1 required positional argument: 'cls' because nothing fills that first parameter. Worse, calling it on an instance passes the instance as cls, and cls("localhost", ...) then tries to call an object like a function and fails. The decorator is what makes Python inject the class.

Q: During code review you find a class with 12 methods and every single one is @staticmethod. What feedback do you give?

The class holds no state, so it is not earning its keep: it is just a namespace. In Python the idiomatic namespace is a module, so the cleaner refactor is to move those functions into a module like validators.py and import them directly. That removes a level of indirection, shortens call sites, and makes the functions easier to test and reuse. A class makes sense only if some of those methods start sharing instance or class data.

Q: Why can a class method not read or modify instance attributes?

Because it never receives an instance. Python hands it only cls, the class object, and a class can have zero, one, or a million instances at call time, so there is no single self it could refer to. It can read and write class-level attributes through cls, and it can create a new instance with cls(...), but it cannot touch the attributes of any existing object unless you pass that object in explicitly as a normal argument.

Q: Can a subclass override a @classmethod or @staticmethod, and does the parent’s factory pick up the override?

Yes, both can be overridden just like normal methods: define a method with the same name in the subclass, keeping the same decorator. And yes, lookups stay polymorphic. If a parent class method calls another method via cls.validate(), and the subclass overrides validate, calling the factory on the subclass runs the subclass version, because cls is the subclass. This is what makes classmethod factories play well with inheritance.

Q: What does @classmethod actually do under the hood when Python sees it?

It wraps the function in a classmethod descriptor object. When you access the method through the class or an instance, the descriptor’s binding logic returns a bound method whose first argument is already set to the class, which is why you never pass cls yourself. @staticmethod is a descriptor too, one that returns the raw function with no binding at all. You do not need descriptor internals daily, but naming them in an interview shows you know decorators here are not magic syntax, they are ordinary objects implementing __get__.

Want more? the official Python documentation documents everything this post could not fit.

Previous: Python: Constructors __init__ & Instance Variables

Next: Python Inheritance: Single, Multi-level, Hierarchical

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *