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.