Instagram has millions of users. Each user has a name, followers and posts, and each can follow, like and comment. Programmers don't write separate code for every user — they write one class called User and create millions of objects from it. This is the core of Object-Oriented Programming (OOP). In this blog you will master classes, objects, __init__, self, attributes, methods and more.
Watch the video first, then use this blog as your notes. Every section has runnable examples, and at the end you will find a "Predict the Output" quiz, practice challenges, a self-check list, interview questions and a quick summary.
How to use this blog: Open the Python shell (type python in your terminal) or any online Python editor and type every example yourself. Change the values, break the code and see what happens — that is how concepts really stick.
A class is a blueprint (a template). An object is a real thing built from that blueprint (an instance).
| Class (blueprint) | Objects (instances) |
|---|---|
| House map drawn by an architect | Your house, your neighbour's house |
| Car design | Riya's red Swift, Aman's white Creta |
| Student | Riya (roll 1), Aman (roll 2) |
| Defined once | Created as many times as you need |
Every object has:
class Student:
pass # an empty class
s1 = Student() # creating an object (instance)
s2 = Student()
print(type(s1)) # <class '__main__.Student'>
print(s1 == s2) # False - two different objectsNaming convention: Class names use PascalCase: Student, BankAccount, ShoppingCart. Variables and methods use snake_case.
__init__ is a special method that runs automatically every time a new object is created. It is used to set the object's initial attributes.
class Student:
def __init__(self, name, roll, marks):
self.name = name # instance attributes
self.roll = roll
self.marks = marks
s1 = Student("Riya", 1, 92)
s2 = Student("Aman", 2, 85)
print(s1.name, s1.marks) # Riya 92
print(s2.name, s2.marks) # Aman 85Student("Riya", 1, 92)
|
v
1. Python creates an empty object
2. Calls __init__(self=<new object>, name="Riya", roll=1, marks=92)
3. Returns the ready object -> stored in s1self refers to the current object — the one the method is working on. When you call s1.show(), Python actually calls Student.show(s1). So inside the method, self is s1.
class Student:
def __init__(self, name):
self.name = name
def greet(self):
print("Hi, I am", self.name)
s1 = Student("Riya")
s1.greet() # Hi, I am Riya
Student.greet(s1) # exactly the same thingRemember: self is just a naming convention (not a keyword), but always use it. Forgetting self in a method definition gives: TypeError: greet() takes 0 positional arguments but 1 was given.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f"Deposited {amount}. Balance: {self.balance}")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient balance!")
else:
self.balance -= amount
print(f"Withdrew {amount}. Balance: {self.balance}")
acc = BankAccount("Riya", 1000)
acc.deposit(500) # Deposited 500. Balance: 1500
acc.withdraw(2000) # Insufficient balance!
acc.withdraw(300) # Withdrew 300. Balance: 1200class Dog:
species = "Canis familiaris" # class attribute - shared by ALL dogs
count = 0
def __init__(self, name):
self.name = name # instance attribute - unique to each dog
Dog.count += 1
d1 = Dog("Tommy")
d2 = Dog("Bruno")
print(d1.species, d2.species) # same for both
print(d1.name, d2.name) # Tommy Bruno
print(Dog.count) # 2| Instance attribute | Class attribute |
|---|---|
| Defined inside __init__ using self. | Defined directly inside the class |
| Different for each object | Shared by all objects |
| Accessed via obj.attr | Accessed via ClassName.attr (or obj.attr) |
| Example: name, roll number | Example: school name, counter of objects |
Trap: Writing d1.species = "X" does not change the class attribute — it creates a new instance attribute on d1 only. Dog.species and d2.species stay unchanged.
class Student:
school = "Coding Battle School"
def __init__(self, name):
self.name = name
def introduce(self): # instance method - uses self
return f"{self.name} studies at {self.school}"
@classmethod
def change_school(cls, new_name): # class method - uses cls
cls.school = new_name
@staticmethod
def is_adult(age): # static method - no self / cls
return age >= 18
s = Student("Riya")
print(s.introduce())
Student.change_school("CB Academy")
print(s.introduce()) # Riya studies at CB Academy
print(Student.is_adult(20)) # True| Type | First parameter | Can access | Use for |
|---|---|---|---|
| Instance method | self | Object + class data | Most behaviour |
| Class method (@classmethod) | cls | Class data | Factory methods, changing class-level data |
| Static method (@staticmethod) | None | Nothing automatically | Utility functions related to the class |
By default, printing an object shows something ugly like <__main__.Student object at 0x7f...>. Define __str__ to control it.
class Book:
def __init__(self, title, price):
self.title = title
self.price = price
def __str__(self): # for users (print)
return f"{self.title} - Rs {self.price}"
def __repr__(self): # for developers (debugging)
return f"Book({self.title!r}, {self.price})"
b = Book("Python Basics", 299)
print(b) # Python Basics - Rs 299
print([b]) # [Book('Python Basics', 299)]s = Student("Riya")
s.name = "Riya Sharma" # modify
s.age = 17 # add a new attribute to this object only
print(hasattr(s, "age")) # True
print(getattr(s, "name")) # Riya Sharma
del s.age # delete
print(s.__dict__) # {'name': 'Riya Sharma'}
print(isinstance(s, Student)) # Trueclass Book:
def __init__(self, title, author):
self.title = title
self.author = author
self.is_issued = False
def __str__(self):
status = "Issued" if self.is_issued else "Available"
return f"{self.title} by {self.author} [{status}]"
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
self.books.append(book)
def issue(self, title):
for book in self.books:
if book.title == title and not book.is_issued:
book.is_issued = True
return f"'{title}' issued successfully"
return f"'{title}' is not available"
def show(self):
for book in self.books:
print(book)
lib = Library()
lib.add_book(Book("Python Crash Course", "Eric Matthes"))
lib.add_book(Book("Clean Code", "Robert Martin"))
print(lib.issue("Clean Code"))
print(lib.issue("Clean Code"))
lib.show()'Clean Code' issued successfully
'Clean Code' is not available
Python Crash Course by Eric Matthes [Available]
Clean Code by Robert Martin [Issued]Don't run the code yet! Write your answers in a notebook, then check the Answers section at the end of this blog.
Q1. What is the output?
class A:
x = 5
a = A()
b = A()
a.x = 10
print(A.x, a.x, b.x)Q2. What is the output?
class Counter:
count = 0
def __init__(self):
Counter.count += 1
Counter(); Counter(); Counter()
print(Counter.count)Q3. What happens?
class Test:
def hello():
print("Hello")
Test().hello()Q4. What is the output?
class P:
def __init__(self, n):
self.n = n
def __str__(self):
return "P" + str(self.n)
print(P(7))Q5. What is the output?
class C:
def __init__(self):
self.items = []
c1, c2 = C(), C()
c1.items.append(1)
print(c2.items)Q6. When is __init__ called?
Q7. Which decorator creates a method that receives the class as the first argument?
Solve these from easy to tricky:
Solutions for challenges 1, 2 and 3 are in the Answers section.
Tick off each skill once you are confident:
Q: What is the difference between a class and an object?
A class is a blueprint that defines attributes and methods; an object is a concrete instance created from the class.
Q: What is self in Python?
A reference to the current instance, passed automatically as the first argument of instance methods.
Q: Is __init__ a constructor?
It is the initializer. The object is actually created by __new__; __init__ then initialises it. In everyday terms it is called the constructor.
Q: Difference between __str__ and __repr__?
__str__ gives a user-friendly string (used by print); __repr__ gives an unambiguous developer representation (used in the shell and in lists).
Q: Can a class have multiple __init__ methods?
No — a later definition replaces the earlier one. Use default arguments or class methods as alternative constructors.
| Concept | Syntax / Key point |
|---|---|
| Define class | class Student: |
| Constructor | def __init__(self, name): |
| Create object | s = Student("Riya") |
| Access attribute | s.name |
| Call method | s.greet() |
| Class method | @classmethod def f(cls): |
| Static method | @staticmethod def f(): |
| Check instance | isinstance(s, Student) |
# Challenge 1
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
r = Rectangle(5, 3)
print(r.area(), r.perimeter()) # 15 16
# Challenges 2 and 3
class Student:
total_students = 0
def __init__(self, name, marks):
self.name = name
self.marks = marks
Student.total_students += 1
def average(self):
return sum(self.marks) / len(self.marks)
s1 = Student("Riya", [90, 85, 95])
s2 = Student("Aman", [70, 80, 75])
print(s1.average()) # 90.0
print(Student.total_students) # 2You can now design your own classes and create objects — the foundation of OOP. Next, learn how to make reproducible random numbers with random.seed(), then continue to Encapsulation, Polymorphism and Inheritance.
If this blog helped you, watch the full video above, subscribe to the channel and share it with a friend who is learning Python. Happy coding!