Classes, __init__ & self
BeginnerClasses bundle data and behaviour: __init__ initializes each instance, self is the explicit instance reference, and attributes live per-instance or shared on the class.
Overview
Python OOP strips Java's ceremony: no access modifiers, no getters/setters by default, no "one public class per file". __init__ is the initializer (the object already exists when it runs), and self — the instance — is passed explicitly as the first parameter of every method. The distinction interviews test: instance attributes (set via self, unique per object) vs class attributes (defined on the class, shared by all instances) — and how a mutable class attribute becomes a shared-state bug.
Defining a Class
Attributes are created by assignment in __init__ (no declaration block). Methods are functions whose first parameter is self. Calling obj.method() passes obj as self automatically.
class BankAccount:
bank_name = "AiCan Bank" # CLASS attribute — shared
def __init__(self, owner, balance=0):
self.owner = owner # INSTANCE attributes
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
self.balance += amount
return self.balance
def __repr__(self): # developer-friendly display
return f"BankAccount({self.owner!r}, {self.balance})"
acc = BankAccount("Asha", 1000)
acc.deposit(500)
print(acc) # BankAccount('Asha', 1500)
print(acc.bank_name) # AiCan Bank — found on the class
print(BankAccount.bank_name) # same
# obj.method(args) is sugar for Class.method(obj, args)
BankAccount.deposit(acc, 100) # works — self passed explicitlyThe Class-Attribute Trap & classmethod/staticmethod
Reading a missing instance attribute falls back to the class — but assigning through self creates an instance attribute that SHADOWS it. Mutable class attributes are shared state: mutate one, see it everywhere. @classmethod receives the class (great for alternate constructors); @staticmethod receives nothing.
class Course:
students = [] # BUG WAITING: shared mutable!
def __init__(self, name):
self.name = name
def enroll(self, who):
self.students.append(who) # mutates the SHARED list
a, b = Course("Java"), Course("Python")
a.enroll("Ravi")
print(b.students) # ['Ravi'] — leaked across instances!
# Fix: create self.students = [] inside __init__
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@classmethod
def margherita(cls): # alternate constructor
return cls(["tomato", "basil"])
@staticmethod
def is_veg(topping): # utility — no self/cls
return topping != "chicken"
p = Pizza.margherita()
print(p.toppings, Pizza.is_veg("basil"))Key Points to Remember
- 1__init__ initializes an already-created object (creation is __new__, rarely touched)
- 2self is explicit — obj.method() is Class.method(obj)
- 3Class attributes are shared; assigning via self shadows them; mutable class attributes are a bug magnet
- 4@classmethod for alternate constructors (cls), @staticmethod for namespaced utilities
Interview Questions
Sign in to ask AriaDifference between class attributes and instance attributes — show the shadowing behaviour.
Why is a mutable class attribute (like a list) dangerous?
When would you use @classmethod vs @staticmethod?
Ask Aria about Classes, __init__ & self
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.