__init__, self, and Methods
Last lesson, you attached data to objects by hand — s1.name = "Aarav" — with nothing forcing consistency. Today, the __init__ constructor makes every object born complete, and self finally gets explained.
__init__ — the birth function
__init__ (double underscore each side — "dunder init") runs automatically when an object is created. It's the setup crew:
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
# creating objects — arguments flow into __init__
s1 = Student("Aarav", 92)
s2 = Student("Diya", 95)
print(s1.name) # Aarav
print(s2.name) # Diya — each object has its OWN dataTrace the flow of Student("Aarav", 92):
__init__(self, "Aarav", 92) — self is the new object, "Aarav" and 92 are your argumentsself.name = name attaches "Aarav" to *that specific object*s1Now every Student is born with name and marks. No object can exist half-formed — the constructor enforces the shape.
self — the object itself
self is the most confusing word for beginners and the simplest idea once it lands: self is the object the method was called on.
class Student:
def __init__(self, name, marks):
self.name = name # "attach name to THIS object"
def introduce(self):
print(f"I'm {self.name}, scored {self.marks}")
s1 = Student("Aarav", 92)
s2 = Student("Diya", 95)
s1.introduce() # I'm Aarav, scored 92
s2.introduce() # I'm Diya, scored 95The trick: s1.introduce() secretly means introduce(s1) — Python passes the object as self automatically. Inside the method, self.name means "the name of *whichever object I was called on*." That's why s1 prints Aarav and s2 prints Diya with the same method.
The two self-rules:
self is always the first parameter of every methodMethods using their own data
Methods become powerful when they compute from the object's attributes:
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
def average(self):
return sum(self.marks) / len(self.marks)
def has_passed(self):
return self.average() >= 40 # methods calling methods!
s1 = Student("Aarav", [85, 90, 78])
print(s1.average()) # 84.33...
print(s1.has_passed()) # Trueself.marks inside average() refers to the calling object's list. And has_passed calls self.average() — objects' methods work together through self.
The full anatomy, one screen
class Student:
def __init__(self, name, marks): # constructor: birth setup
self.name = name # attributes: the data
self.marks = marks
def introduce(self): # method: behavior
print(f"I'm {self.name}")
def average(self): # method computing from data
return sum(self.marks) / len(self.marks)
s1 = Student("Aarav", [85, 90, 78]) # instantiation
s1.introduce() # method callRead it as a sentence: *"A Student is born with a name and marks; it can introduce itself and compute its average."*
Common Errors & Fixes
__init__ with a typo (_init_, __init_). Dunders need double underscores on both sides.self as the method's first parameter.✅ Checkpoint
__init__ run? *(Automatically, at object creation)*self? *(The object the method was called on — passed automatically)*Next: inheritance — building new classes on top of existing ones.