Learn by reading through in order

Instance, Class, and Static Methods — The Three Method Types

Learn Python's three method types — instance, class, and static — with diagrams. Walk through using self / cls / no first argument and when to choose each.

Last time we covered the difference between class and instance variables. Every method we've written so far in def method(self, ...) form is an instance method, but Python classes have two more kinds — class methods and static methods. This time we'll sort out the three types and how to choose between them.

There Are Three Kinds of Methods

Methods in a Python class fall into three kinds, distinguished by what they take as their first argument:

- Instance method — first parameter self. Receives the instance itself.

- Class method — first parameter cls. Receives the class itself. Decorated with @classmethod.

- Static method — no first parameter. Takes neither self nor cls. Decorated with @staticmethod.

The kind determines what each method can reach. Only instance methods can touch instance variables self.x. Class methods are how you work with class variables from the class side, and static methods are for pure logic that touches neither.

What the Three Method Types Can Reach
Instancemethod (self)instance vars+ class vars+ argsClassmethod (cls)class vars+ argsStaticmethod (none)args onlyreachesreachesreaches
Instance methods reach instance variables + class variables + arguments — everything. Class methods reach class variables + arguments. Static methods reach arguments only.

Instance Methods — Taking self

The def method(self, ...) form you've been writing all along is an instance method. The first parameter self is filled in automatically with the instance the method was called on, so you can reach instance variables as self.name.

When you write apple.show(), Python internally translates it to Product.show(apple). You can think of methods as "functions that take an instance as their first argument."

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def total_with_tax(self, tax_rate):    # instance method
        return int(self.price * (1 + tax_rate))

apple = Product("apple", 150)
print(apple.total_with_tax(0.1))  # 165

# Internally the same as this
print(Product.total_with_tax(apple, 0.1))  # 165
How self Lets You Reach Instance Variables
__init__self.price = 150appleprice = 150apple.total_with_tax(0.1)self getsappleself.price→ 150①set②call③read
__init__ sets apple.price = 150 → ② calling apple.method(0.1) puts apple into self → ③ self.price reads the value set on the instance.

Class Methods — @classmethod and cls

A class method is defined with the @classmethod decorator, with cls as the first parameter (a conventional name). cls automatically holds the class itself — in this case the Product blueprint.

Use class methods when you want to read or write a class variable or build and return a single instance (a factory). You can also call them through an instance (apple.method()), but calling them on the class as Product.method() is the standard.

class Product:
    total_count = 0           # class variable

    def __init__(self, name, price):
        self.name = name
        self.price = price
        Product.total_count += 1

    @classmethod              # ① returns a class variable
    def get_total(cls):
        return f"{cls.total_count} products registered"

    @classmethod              # ② builds and returns an instance (factory)
    def from_csv_row(cls, row):
        name, price = row.split(",")
        return cls(name, int(price))

Product("apple", 150)
Product("banana", 80)

print(Product.get_total())             # 2 products registered
orange = Product.from_csv_row("orange,120")
print(orange.name, orange.price)        # orange 120
How a Class Method Works
@classmethoddecoratordef get_total( cls):Product.get_total()cls = Product(the class)cls.total_count(class var)declareautovia lookup
Calling get_total(cls) (decorated with @classmethod) as Product.get_total() automatically puts the Product class itself into cls. From there you can reach class variables as cls.total_count.
self vs cls
Instance method
  • def total_with_tax(self, tax_rate):
  • Call: apple.total_with_tax(0.1)
  • self becomes apple
  • Reaches the instance variable self.price
Class method
  • Decorated with @classmethod
  • def get_total(cls):
  • Call: Product.get_total() (from the class)
  • cls becomes Product
  • Reaches the class variable cls.total_count
self is "the instance you called from"; cls is "the class itself." When the work is purely about class variables, going through cls makes the intent clearer.

Write a class method get_total that returns "the number of products created so far."

① In class Product:, add a class variable total_count = 0.

② In __init__(self, name, price), after self.name = name / self.price = price, write Product.total_count += 1.

③ Define get_total decorated with @classmethod and first parameter cls, returning cls.total_count.

④ Build Product("apple", 150) and Product("banana", 80), then print(Product.get_total()) and confirm it shows 2.

(If you run it correctly, an explanation will appear.)

Python Editor

Run code to see output

Static Methods — @staticmethod

A static method is defined with @staticmethod and takes neither self nor cls. It's "a plain function that lives in the class" — completes its work using only its arguments, with no access to instance or class variables.

It fits cases where the work is related to the class but doesn't depend on a particular instance or class state — input validation, formatting, pure computations using constants. Use it to keep utility functions next to the related class instead of writing them as standalone functions.

class Product:
    def __init__(self, name, price):
        if not Product.is_valid_price(price):
            raise ValueError(f"invalid price: {price}")
        self.name = name
        self.price = price

    @staticmethod
    def is_valid_price(price):           # no self, no cls
        return 0 <= price <= 1_000_000

print(Product.is_valid_price(150))      # True
print(Product.is_valid_price(-1))       # False

# Product("apple", -1)  # ValueError: invalid price: -1

Static Methods Work from the Class or the Instance

Both Product.is_valid_price(150) and apple.is_valid_price(150) work the same. But since the method doesn't actually use the instance, calling it on the class makes the intent clearer. Writing apple.is_valid_price(...) makes a reader pause and wonder "is this depending on apple's state?" — best avoided.

Write a static method for price validation.

① In class Product:, define is_valid_price(price) as a @staticmethod that returns 0 <= price <= 1_000_000.

② At the top of __init__(self, name, price), write if not Product.is_valid_price(price): raise ValueError("invalid price") before assigning self.name / self.price.

③ Confirm Product("apple", 150) succeeds and Product("sea_urchin", -1) fails with ValueError by wrapping it in try / except.

Python Editor

Run code to see output

Choosing Between the Three

Which one to use can be decided mechanically based on "what the method needs to touch."

- Touches instance variables (self.x)instance method

- Only touches class variables (cls.x)class method

- Touches neither (just argument-driven)static method

When in doubt: first check whether you touch instance variables. If not, check class variables. If neither, static. That order won't steer you wrong.

Decision Flow for Method Types
touchself.x?Instancemethodtouchcls.x?ClassmethodtouchesneitherStaticmethodpure logicliving in the classYesNoYesNouse
First branch: do you touch instance variable self.x? Next: do you handle class variables? If neither, static is the right pick.
TypeDecorator1st parameterTypical use
Instance methodnoneselfRegular methods that read/write self.x
Class method@classmethodclsRead/write class variables, factories
Static method@staticmethodnoneValidation, formatting, pure logic

Put all three method types in one class and feel the differences.

① Define class Product: with class variable total_count = 0 and __init__(self, name, price). At the top of __init__, raise ValueError if Product.is_valid_price(price) fails; otherwise assign self.name = name / self.price = price and run Product.total_count += 1.

② Add an instance method total_with_tax(self, tax_rate) returning int(self.price * (1 + tax_rate)).

③ Add a class method get_total(cls) decorated with @classmethod that returns cls.total_count.

④ Add a static method is_valid_price(price) decorated with @staticmethod that returns 0 <= price <= 1_000_000.

⑤ Build apple = Product("apple", 150) and print both apple.total_with_tax(0.1) and Product.get_total().

Python Editor

Run code to see output

With this article you've covered the three method types — instance / class / static — and how to choose between them. That rounds out the core toolkit of classes, instances, attributes, and methods in Python. From here, you can move into the three pillars of object-oriented programming: encapsulation (controlling attribute visibility), inheritance (carrying over capabilities from another class), and polymorphism (the same method name behaving differently per type).

QUIZ

Knowledge Check

Answer each question one by one.

Q1Which decorator do you use to define a static method?

Q2Which method is the most natural fit for @classmethod?

Q3Which methods can be called without creating an instance? (Pick the most appropriate single answer if multiple apply.)