
In Part 42 we drew the big picture: a class is a job role, an object is an employee, and a class is your own data type. Now the concrete why, then the mechanics — proven in code, exactly as in the master deck.
Functions are great — but they have one big pain.
A function is a machine: input → output → then it forgets everything. It keeps no memory between calls.
def deposit(amount):
balance = 0 # re-made as 0 EVERY call
balance += amount
return balance
print(deposit(500)) # 500
print(deposit(500)) # 500 ← forgot the last one!
An object remembers:
class Account:
def __init__(self):
self.balance = 0 # runs ONCE, at creation
def deposit(self, amount):
self.balance += amount
return self.balance
acc = Account()
print(acc.deposit(500)) # 500
print(acc.deposit(500)) # 1000 ← remembered!
The secret is where = 0 runs. In the function it re-runs every call (reset). In the object, __init__ runs once; after that the balance lives inside acc and survives.
Every real thing = DATA + ACTIONS + RULES, kept together. With loose functions they drift apart, and nothing enforces the rule:
balance = 1000
def deposit(bal, amount):
return bal + amount
balance = -9999 # allowed
balance = "oops" # allowed! nothing protects the data
A class bundles all three into one safe unit, and the rule is enforced:
class BankAccount:
def __init__(self, balance):
self.balance = balance # DATA inside
def deposit(self, amount): # ACTION with data
self.balance += amount
def withdraw(self, amount):
if amount > self.balance: # RULE built in
raise ValueError("Not enough money")
self.balance -= amount
A function only ever acts on types that already exist — int, str, list. It can never add a new one. A function gives you a verb (an action); only a class gives you a new noun — a brand-new data type. Let us prove that now.
This is the exact spot most learners get lost. Let us settle three words for good.
Every object is exactly three things: an identity, a type, and a value. The methods are not a fourth part — they live inside the type, written once and shared by every object of that type.
nums = [1, 2, 3]
print(type(nums)) # <class 'list'> ← (1) TYPE what kind
print(id(nums)) # 140412... ← (2) IDENTITY which one / where
print(nums) # [1, 2, 3] ← (3) VALUE the contents
# append, sort, pop ... do NOT live in THIS list —
# they live in the type `list`, shared by every list.
A str has the same three things but a different type, so a different set of methods (upper, split, replace ...).
type = "what kind" = data type.
dir(x)prints the full menu a type offers.
class BankAccount: — tell Python "a new type is coming."__init__: gives each object its starting data; runs automatically when an object is made.self: means "this particular object" — so each account keeps its own data.class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Not enough money")
self.balance -= amount
acc = BankAccount("Alice", 5000) # __init__ runs automatically
acc.deposit(500)
print(acc.owner, acc.balance) # Alice 5500
print(type([1, 2, 3])) # <class 'list'>
print(type(acc)) # <class 'BankAccount'> ← YOUR type, equally real
Python treats your BankAccount exactly like its built-in list. So when you see class, do not think "blueprint" — think: "I am teaching Python a new data type." This is true even for an empty class (class Empty: pass still produces real objects with type + identity + value).
acc1 = BankAccount("Alice", 5000) # object 1 — its own data
acc2 = BankAccount("Ravi", 2000) # object 2 — its own data
print(acc1.owner, acc2.owner) # Alice Ravi — never mixed up
One design → many objects: separate data, shared actions. (Same as list being the idea and [1, 2, 3] being one real list.)
classclass is live code that builds an object. When Python reaches a class block, it runs the body, gathers what you defined into a dictionary, and hands it to a built-in factory called type, which manufactures the class and stores it under your name.
class Dog:
legs = 4
# is really shorthand for:
Dog = type("Dog", (), {"legs": 4})
So a class is itself an object:
print(type(Dog)) # <class 'type'>
Two floors: type makes classes, and classes make objects. (int, str, list were all made by type too.)
__init__ vs __new__ — Initializer, Not ConstructorPeople call __init__ the "constructor." Not quite:
__new__ → the real constructor: it creates the empty object and returns it. Runs first.__init__ → the initializer: it fills the object that already exists. Runs second, returns None.__new__ builds the empty box; __init__ puts things in it. You almost always write only __init__ — which is exactly why everyone mistakes it for the constructor.
self Is Not Magic (and Not a Keyword)When you write acc.deposit(500), Python rewrites it as:
BankAccount.deposit(acc, 500) # the object before the dot becomes the first argument
We name that first argument self — by convention, not law. Whatever sits before the dot becomes self. So self is this account (acc), not the class.
self.x = ... → lives on that one object.class Dog:
species = "Canis" # class data — shared by all dogs
def __init__(self, name):
self.name = name # instance data — one per dog
Trap: a mutable class attribute is shared. tricks = [] in the class body → every dog shares ONE list. Fix: put per-object state in __init__ → self.tricks = [].
A function stands alone; a method is a function inside a class, called on an object. There are exactly three kinds, chosen by one question — what does this job need?
class BankAccount:
bank_name = "OnePercent Bank" # class data — shared by every account
interest_rate = 5 # class data — shared by every account
count = 0 # class data — how many exist so far
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
BankAccount.count += 1
# INSTANCE · needs THIS account (self)
def deposit(self, amount):
self.balance += amount
# CLASS · needs the class (cls), not one account
@classmethod
def savings(cls, owner): # reason 1: another DOOR to create one
return cls(owner, 0) # cls(...) == BankAccount(...) -> new object
@classmethod
def set_interest_rate(cls, rate): # reason 2: a setting shared by ALL accounts
cls.interest_rate = rate
@classmethod
def total_accounts(cls): # reason 3: a fact about the WHOLE class
return cls.count
# STATIC · needs neither self nor cls
@staticmethod
def is_valid_amount(amount): # just numbers in -> answer out
return amount > 0
| The job is about… | Example | Use | First word |
|---|---|---|---|
| one specific object | alice.deposit(500) | instance method | self |
| creating an object (a new "door") | BankAccount.savings("Alice") | class method | cls |
| a setting shared by all | BankAccount.set_interest_rate(7) | class method | cls |
| a fact about the whole class | BankAccount.total_accounts() | class method | cls |
| numbers in → answer out | BankAccount.is_valid_amount(500) | static method | none |
Four ideas that make it click:
deposit needs self (a real account). A class method is the "open the account" step: it uses cls, so it needs no account yet.self / cls are written but never passed — they come from before the dot. BankAccount.savings("Alice") runs as savings(BankAccount, "Alice").() = make a new one · . = reach into an existing one. cls(owner, 0) builds an account; cls.interest_rate = rate changes shared data (the twin of self.balance += amount).__str__ ...), @property, @abstractmethod are not a fourth kind — they are flavors layered on top, covered in later parts.Memory hook: self → this one account · cls → the whole bank · no first word → a calculator inside the class.
When you build your program around such objects — things that carry their own data and actions — you are doing Object-Oriented Programming. The order settles the common doubt "class first, or OOP first?":
class (the tool) → object (the real thing) → apply 4 ideas → = OOP.
OOP is a formula: four pillars applied on top of classes and objects to keep code clean as it scales:
len() on a str, list, or dict).You have already met all four in Python's own types — OOP is not new magic; it is what Python already does, now in your hands.
You did not start OOP today — you have used objects since Part 5. Every str, list, and dict is an object. Every exception you have ever caught is an object.
In Part 35 you even wrote your own class without thinking of it that way:
class InvalidAgeError(Exception):
pass
InvalidAgeError is a class built on Exception; each raise InvalidAgeError("...") makes an object of that class. Python has been object-oriented the whole time — the only new thing is that the tools are now in your hands.
class User(models.Model): — and each row becomes an object.@classmethod): datetime.now(), dict.fromkeys(), Django's Model.objects.create().Extend the BankAccount class:
bank_name and count (total accounts created).@classmethod premium(owner) that opens an account with a starting balance of 1000.@staticmethod is_valid_amount(amount) returning whether an amount is positive.deposit(amount) that checks is_valid_amount before adding.premium), deposit into each, and print BankAccount.count.In a comment, label each method as instance / class / static and write its "first word" (self / cls / none).
Save as src/methods_three_kinds.py.
Next: the four pillars of OOP — Encapsulation, Inheritance, Polymorphism, and Abstraction — each with a diagram and runnable code.
Production Codeನಲ್ಲಿ Polymorphism ಯಾಕೆ Powerful ? OOP Master Flow | Part-46
Part 46
Production Codeನಲ್ಲಿ Polymorphism ಯಾಕೆ Powerful ? OOP Master Flow | Part-46
Part 46