The Story That Explains These Three Tools
But every store shares the same menu, the same prices, the same logo — these are class variables, one truth shared across the whole chain.
When a customer orders a latte at the Mumbai store, only that store's revenue moves. That's an instance method — behaviour that runs on one specific store. But when HQ raises the latte price from $4.50 to $5.00, it applies to every store instantly. That's a class method — one call, whole chain updates.
Instance methods = behaviour of one object. Class variables = data shared by every object. Class methods = behaviour that acts on the class itself (or creates new objects). Master these three and you can express almost any real-world domain in Python.
Where We Are — What You Already Know
You already know how to write a class with __init__ and instance variables.
Something like this:
class CoffeeShop:
def __init__(self, city, staff_count):
self.city = city # instance variable — per store
self.staff_count = staff_count # instance variable — per store
self.daily_revenue = 0.0 # instance variable — per store
store1 = CoffeeShop("Mumbai", 8)
store2 = CoffeeShop("London", 12)
This gives you objects that hold data. What we need next is objects that can also do things (normal methods), classes that hold data shared by every instance (class variables), and methods that own the class itself (class methods). Let's take them one at a time.
Normal Methods (Instance Methods) — Behaviour Attached to Each Object
A normal method — also called an instance method — is just a
function defined inside a class whose first parameter is self. When you call it via
obj.method(), Python transparently passes obj in as self,
so the method knows which specific object it's working on.
def serve(self, item):
self — receives the object the method was called on
self.x to read/write that object's own instance variables
store1.serve("latte") — Python passes store1 as self
class CoffeeShop:
def __init__(self, city):
self.city = city
self.daily_revenue = 0.0
self.orders = []
# ── Normal (instance) method — acts on THIS store ──
def serve(self, item, price):
self.daily_revenue += price
self.orders.append(item)
print(f"[{self.city}] Served {item} for ${price}")
# ── Another instance method that reports state ──
def summary(self):
print(f"{self.city}: {len(self.orders)} orders, ${self.daily_revenue}")
# Create two independent stores
mumbai = CoffeeShop("Mumbai")
london = CoffeeShop("London")
mumbai.serve("latte", 4.5) # self = mumbai
mumbai.serve("muffin", 3.5) # self = mumbai
london.serve("espresso", 3.0) # self = london
mumbai.summary() # uses Mumbai's data only
london.summary() # uses London's data only
Animated Diagram — An Instance Method Acts on One Specific Object
Watch mumbai.serve("latte", 4.5) flow: Python passes mumbai in as
self, and the method mutates only that store's revenue. London and Tokyo aren't
even aware anything happened.
Only Mumbai's revenue moves. London and Tokyo are untouched because self pointed only to Mumbai.
Class Variables — One Copy, Shared by Every Object
A class variable is defined at the class level, not inside
__init__. There's exactly one copy and every instance can see it.
Think of it as "facts that belong to the concept, not to any individual object."
class CoffeeShop:
# ── Class variables — declared at the class level, ONE copy total ──
company_name = "Bean There"
menu = {"latte": 4.50, "espresso": 3.00, "muffin": 3.50}
total_stores = 0
def __init__(self, city):
# ── Instance variables — one copy per object ──
self.city = city
self.daily_revenue = 0.0
# Bump the shared counter when a new store opens
CoffeeShop.total_stores += 1
mumbai = CoffeeShop("Mumbai")
london = CoffeeShop("London")
tokyo = CoffeeShop("Tokyo")
# Access class variables through the class itself
print(CoffeeShop.company_name) # Bean There
print(CoffeeShop.total_stores) # 3
print(CoffeeShop.menu["latte"]) # 4.5
# You CAN also access them through any instance — they're shared
print(mumbai.company_name) # Bean There
print(london.menu["latte"]) # 4.5 — same object as CoffeeShop.menu
Class variable — one copy total, shared. Written directly under the
class line: menu = {...}.
Instance variable — one copy per object. Written with self.
inside a method: self.city = city. If you change your mind about which one
you need, that's a design decision — not a syntax fix.
Animated Diagram — Class Variable Is Shared, Instance Variable Is Independent
Watch two events. First, HQ updates the shared menu (CoffeeShop.menu["latte"] = 5.00) —
all three stores see the new price instantly because they share one menu.
Then Mumbai serves a latte — only Mumbai's revenue moves because
daily_revenue is per-store.
One menu update ripples to every store. But daily_revenue lives per-store — Mumbai's rings up, London and Tokyo stay at zero.
The Two Gotchas of Class Variables
Class variables are simple until you try to change them the wrong way. Two traps bite almost every Python beginner. Both come from Python's rule that reading uses class fallback, but writing creates an instance variable.
Gotcha #1 — Writing Through an Instance Creates a Shadow
class CoffeeShop:
total_stores = 0 # class variable
s1 = CoffeeShop()
s2 = CoffeeShop()
# WRONG: this does NOT update the shared counter
s1.total_stores = 99 # creates a NEW instance variable on s1
print(s1.total_stores) # 99 (shadow — s1's own attribute)
print(s2.total_stores) # 0 (still reading class var)
print(CoffeeShop.total_stores) # 0 (class var untouched)
# RIGHT: update via the class itself
CoffeeShop.total_stores = 99
print(s2.total_stores) # 99 (all instances see it now)
Writing instance.attr = value always creates an instance
variable — even if a class variable with the same name exists. To modify a class variable,
write it through the class: ClassName.attr = value.
Gotcha #2 — Mutable Class Variables Are Shared State
class CoffeeShop:
orders = [] # class variable — a list (mutable!)
def __init__(self, city):
self.city = city
s1 = CoffeeShop("Mumbai")
s2 = CoffeeShop("London")
s1.orders.append("latte") # mutates the SHARED list in place!
print(s1.orders) # ['latte']
print(s2.orders) # ['latte'] ← ??!
print(s1.orders is s2.orders) # True — same list object!
Never put a mutable object (list, dict, set) as a
class variable unless you truly want every instance to share and mutate the same
one. For per-instance lists, create them in __init__:
self.orders = []. Only use class variables for immutable shared data
like strings, numbers, or tuples — or for genuinely global registries.
Class Methods — Functions That Own the Class Itself
A class method is a method whose first parameter is the class
itself (conventionally named cls), not an instance. You mark it with the
@classmethod decorator. You call it on the class, not on an object.
Two overwhelmingly common use cases:
CoffeeShop.from_csv("Mumbai,8")
parses a string and returns a new store. Keeps the parsing logic tied to the class.
CoffeeShop.update_price("latte", 5.00) raises the price for the whole chain.
CoffeeShop.how_many_open() reports the total count without needing a specific store.
class CoffeeShop:
company_name = "Bean There"
menu = {"latte": 4.50, "espresso": 3.00}
total_stores = 0
def __init__(self, city, staff_count):
self.city = city
self.staff_count = staff_count
self.daily_revenue = 0.0
CoffeeShop.total_stores += 1
# ── Class method as ALTERNATE CONSTRUCTOR ──
@classmethod
def from_csv(cls, row: str):
"""Build a CoffeeShop from a CSV row like 'Mumbai,8'."""
city, staff = row.split(",")
return cls(city, int(staff)) # cls() == CoffeeShop()
# ── Class method for CLASS-WIDE OPERATION ──
@classmethod
def update_price(cls, item, new_price):
"""HQ raises a price everywhere at once."""
cls.menu[item] = new_price
print(f"HQ: {item} is now ${new_price} at every store")
# ── Class method for AGGREGATE QUERY ──
@classmethod
def how_many_open(cls):
return cls.total_stores
# ── Regular constructor ──
mumbai = CoffeeShop("Mumbai", 8)
# ── Alternate constructor (class method) ──
london = CoffeeShop.from_csv("London,12")
tokyo = CoffeeShop.from_csv("Tokyo,20")
# ── Class-wide operation ──
CoffeeShop.update_price("latte", 5.00)
# ── Aggregate query ──
print(CoffeeShop.how_many_open()) # 3
print(mumbai.menu["latte"]) # 5.0 (Mumbai sees the update)
Animated Diagram — self vs cls
The difference in one picture. An instance method receives self
— a specific existing object. A class method receives cls
— the class itself, which it uses to read shared state or build brand-new objects.
self = "which object am I working on?" · cls = "which class am I working on?"
Why cls and Not Just CoffeeShop?
Inside a class method you could technically hard-code the class name — but that breaks
inheritance. cls always refers to the actual class the method
was called on, which is the point.
class CoffeeShop:
company_name = "Bean There"
@classmethod
def from_csv(cls, row):
city, staff = row.split(",")
return cls(city, int(staff)) # ← uses cls, not CoffeeShop
class PremiumCoffeeShop(CoffeeShop): # subclass
company_name = "Bean There Premium"
# Because from_csv uses cls, this returns a PremiumCoffeeShop — not a CoffeeShop
p = PremiumCoffeeShop.from_csv("Milan,10")
print(type(p).__name__) # PremiumCoffeeShop
print(p.company_name) # Bean There Premium
Inside a @classmethod, always use cls — never the hard-coded class
name. It costs nothing to write and makes your class play nicely with future subclasses.
Instance Method vs Class Method — At a Glance
| Question | Instance Method | Class Method |
|---|---|---|
| Decorator required? | No | @classmethod |
| First parameter | self — the object |
cls — the class |
| Called via | obj.method() |
Class.method() (or obj.method()) |
| Needs an existing object? | Yes | No |
| Can read/write instance vars? | Yes — via self.x |
No — no self |
| Can read/write class vars? | Read yes; write only via class name | Yes — via cls.x |
| Can create new instances? | Rarely (usually not the point) | Yes — return cls(...) |
| Typical use case | Behaviour on this one object | Alternate constructor, class-wide operation, aggregate |
Complete Practical Example — All Three Together
One class that uses instance methods, class variables, and class methods in a realistic small-domain example. Ready to paste, run, and read line by line.
class CoffeeShop:
# ══ CLASS VARIABLES — shared across every store ══
company_name = "Bean There"
menu = {"latte": 4.50, "espresso": 3.00, "muffin": 3.50}
tax_rate = 0.10
total_stores = 0
def __init__(self, city, staff_count):
# ══ INSTANCE VARIABLES — one copy per store ══
self.city = city
self.staff_count = staff_count
self.daily_revenue = 0.0
self.orders = [] # fresh list per instance
CoffeeShop.total_stores += 1
# ══ INSTANCE METHOD — acts on THIS store ══
def serve(self, item):
if item not in CoffeeShop.menu:
raise ValueError(f"{item} not on menu")
price = CoffeeShop.menu[item] * (1 + CoffeeShop.tax_rate)
self.daily_revenue += price
self.orders.append(item)
print(f"[{self.city}] Served {item} for ${price:.2f}")
# ══ INSTANCE METHOD — reports THIS store's state ══
def report(self):
print(f"{self.city:8s} orders={len(self.orders):2d} "
f"revenue=${self.daily_revenue:7.2f}")
# ══ CLASS METHOD — alternate constructor ══
@classmethod
def from_csv(cls, row: str):
city, staff = row.split(",")
return cls(city.strip(), int(staff))
# ══ CLASS METHOD — HQ changes shared state ══
@classmethod
def update_price(cls, item, new_price):
cls.menu[item] = new_price
print(f"HQ: {item} is now ${new_price:.2f} everywhere")
# ══ CLASS METHOD — aggregate query ══
@classmethod
def chain_stats(cls):
return f"{cls.company_name}: {cls.total_stores} stores open"
# ── Open three stores — two ways ─────────────────────────────
mumbai = CoffeeShop("Mumbai", 8) # normal constructor
london = CoffeeShop.from_csv("London, 12") # class-method constructor
tokyo = CoffeeShop.from_csv("Tokyo, 20")
# ── Serve some drinks (instance methods on each store) ───────
mumbai.serve("latte")
mumbai.serve("muffin")
london.serve("espresso")
tokyo.serve("latte")
# ── HQ raises the latte price (class method → class variable) ─
CoffeeShop.update_price("latte", 5.00)
tokyo.serve("latte") # now costs $5.00 + tax
print()
for store in (mumbai, london, tokyo):
store.report()
print()
print(CoffeeShop.chain_stats())
Every store served its own orders and tracked its own revenue independently
(instance methods, instance variables). They all read from one shared menu and one shared
tax rate (class variables). HQ raised the latte price with a single call
(class method modifying a class variable), and both alternate creation via
from_csv and aggregate reporting via chain_stats ran on the
class itself — no specific store needed.
When to Use Which — Decision Grid
from_csv, from_dict, from_json, today().
Keeps the parsing/validation right where the class lives.@classmethod just because it "feels neater" removes
access to self.self.total_stores = 99 creates a shadow, not an update.
To modify a class variable, write it through the class:
ClassName.total_stores = 99 — or via cls.total_stores inside a class method.Common Mistakes (and Fixes)
| Mistake | What Goes Wrong | Fix |
|---|---|---|
Forgetting self in a method |
TypeError: takes 0 positional arguments but 1 was given |
Add self as the first parameter |
self.total = 99 to update class var |
Creates instance shadow — other instances unchanged | ClassName.total = 99 |
Mutable class variable (orders = []) |
Every instance shares — one .append() hits all |
Create self.orders = [] in __init__ |
Forgetting @classmethod |
Python treats it as instance method — first arg is self |
Add @classmethod decorator on the line above |
| Hard-coding class name inside class method | Subclasses build the wrong type | Use cls — that's why it's there |
Calling self.method() forgetting the () |
Returns the bound method object, not its result | Add the parentheses: self.method() |
@classmethod that never touches cls |
You probably wanted @staticmethod |
Either use cls, or switch to @staticmethod |
Quick Reference
| Task | Syntax | Notes |
|---|---|---|
| Define instance method | def serve(self, item): | First arg always self |
| Call instance method | store.serve("latte") | Python passes store as self |
| Read instance variable inside | self.city | Uses this object's memory |
| Declare class variable | menu = {...} under class line | One copy, shared by every instance |
| Read class variable | ClassName.menu or self.menu | Both work for reading |
| Update class variable | ClassName.menu["x"] = ... | Never self.menu = ... |
| Define class method | @classmethod + def m(cls, ...) | Decorator above def |
| Alternate constructor | return cls(...) | Use cls, not ClassName |
| Call class method | ClassName.from_csv("...") | No instance needed |
| Count instances | Increment class var in __init__ | ClassName.total += 1 |
Golden Rules
self
= one specific object. cls = the class itself. There's no
third option in this tutorial.
@classmethod when you don't need self — usually
an alternate constructor, a class-wide operation, or an aggregate query.
__init__ as an instance variable.
__init__ as
self.x = [] so each object gets its own copy.
CoffeeShop.total_stores += 1, never self.total_stores += 1.
The instance form silently creates a shadow attribute and leaves the class variable untouched.
@classmethod on the line above def, and
name the first parameter cls. Skipping either one breaks the method in a
confusing way that Python won't clearly diagnose.
cls(...) to build new objects, not
ClassName(...). That single choice preserves correct behaviour under
inheritance — free, at no code cost.
Employee.from_csv,
Date.today) over "helper functions" outside the class. They keep parsing and
building logic tied to the type they produce.
store.company_name) is fine —
Python falls back to the class automatically. But the moment you write
store.company_name = "...", you've made a per-instance attribute.
Read anywhere; write only via the class.