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 data
Trace the flow of Student("Aarav", 92):
- Python creates a fresh, empty Student object
- It calls
__init__(self, "Aarav", 92)— self is the new object, “Aarav” and 92 are your arguments - Inside,
self.name = nameattaches “Aarav” to that specific object - The finished object is returned to
s1
Now 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 95
The 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:
selfis always the first parameter of every method- You never pass it yourself — Python does, via the dot
Methods 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()) # True
self.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 call
Read it as a sentence: “A Student is born with a name and marks; it can introduce itself and compute its average.”
Common Errors & Fixes
TypeError: Student() takes no arguments— you defined__init__with a typo (_init_,__init_). Dunders need double underscores on both sides.NameError: name 'self' is not defined— you forgotselfas the method’s first parameter.TypeError: introduce() takes 0 positional arguments but 1 was given— defined without self but called via the dot (which passes the object). Add self.- Attribute “disappears” — you set it on one object, read it from another. Each object’s attributes are its own.
✅ Checkpoint
- When does
__init__run? (Automatically, at object creation) - What is
self? (The object the method was called on — passed automatically) - Why does s1.introduce() print Aarav and s2.introduce() print Diya? (self refers to the calling object each time)
- Can a method call another method? (Yes — through self)
Next: inheritance — building new classes on top of existing ones.