What is the difference between an instance method, a class method, and a static method in Python?
Instance methods receive the instance as the first argument (self) and can read and modify instance state. Class methods receive the class as the first argument (cls) via @classmethod and are used for alternative constructors or class-level operations. Static methods receive no implicit argument and are plain functions namespaced inside a class.
How to think about it
Strip the question to its core and it asks one thing: what implicit first argument does Python inject, and why? That choice — made before your method body even runs — is the whole distinction.
- no decorator → instance method → Python passes the calling instance as
self @classmethod→ Python passes the class ascls@staticmethod→ Python passes nothing extra; it’s a plain function that happens to live in the class
A worked example
One Circle class using all three, plus the reason cls matters:
import math
class Circle:
_unit = "cm" # class attribute, shared by all instances
def __init__(self, radius):
self.radius = radius # instance attribute
# Instance method — needs self (per-instance state)
def area(self):
return math.pi * self.radius ** 2
def describe(self):
return f"Circle(r={self.radius} {Circle._unit})"
# Class method — receives the class; the idiomatic "alternative constructor"
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2) # cls(...) — not Circle(...) — so subclasses work
@classmethod
def set_unit(cls, unit):
cls._unit = unit # mutates class-level state
# Static method — no implicit arg; a utility that belongs here by topic
@staticmethod
def validate_radius(r):
return r > 0
c1 = Circle(5)
print("area :", round(c1.area(), 2))
print("describe :", c1.describe())
c2 = Circle.from_diameter(10) # alternative constructor
print("from_dia :", c2.radius)
print("valid(5) :", Circle.validate_radius(5))
print("valid(-1):", Circle.validate_radius(-1))
Circle.set_unit("m") # change the shared class attribute
print("after set_unit:", c1.describe()) # every instance sees it
print(" ", c2.describe())
area : 78.54
describe : Circle(r=5 cm)
from_dia : 5.0
valid(5) : True
valid(-1): False
after set_unit: Circle(r=5 m)
Circle(r=5.0 m)
set_unit changes one class attribute and both circles report the new unit — class-level state in action. And from_diameter is the workhorse use of @classmethod: a second way to build the object, written once.
| First arg | Decorator | Typical use | |
|---|---|---|---|
| Instance | self | none | read/write instance state |
| Class | cls | @classmethod | alternative constructors, class-level state |
| Static | — | @staticmethod | utilities that touch neither self nor cls |
Why cls beats hardcoding the class name
The payoff hides in from_diameter. Because it calls cls(diameter / 2) instead of Circle(diameter / 2), it returns whatever class invoked it. Subclass Circle as Sphere and Sphere.from_diameter(10) builds a Sphere — cls is Sphere. Hardcode Circle(...) and the factory would stubbornly return a Circle no matter who called it.