Object Oriented Programming Python Tutorial, Step by Step: learn OOP in Python. Videos, Notes, and Practical Examples are Included. Read Below Blog 🙂
🔹 What is Object Oriented Programming (OOP)?
OOP (Object-Oriented Programming) is a way to write code by creating objects that represent real-world things.
🧠 Real-life example:
Imagine you’re designing a car. You don’t describe each car from scratch. You define a blueprint (class), and then you can make many cars (objects) using that blueprint.
💡 Simple definition:
“OOP helps organize code by grouping related data and actions into objects.”
✅ Why do we use Object Oriented Programming Python?
Because it helps us write code that is:
- 1. Organized 📦
- We group related data and actions into one place (a class), like packing everything about a “Car” in one box.
- 2. Reusable 🔁
- Once we create a class (blueprint), we can make many objects from it — no need to write the same code again and again.
- 3. Easier to Maintain 🛠️
- If something breaks or needs an update, we only change it in one place — inside the class — not everywhere in the code.
- 4. Real-World Modeling 🌍
- We can write code that looks like real-life things — like “Person”, “Dog”, “Account”, etc. — making it easier to understand.
- 5. Safe 🔐
- We can hide sensitive information inside a class (encapsulation), like hiding your bank balance from direct access.
🧠 Analogy:
Think of OOP like Lego blocks — once you build a block (class), you can reuse it, extend it, or protect parts of it — instead of rebuilding every time.
🧠 Core Types of Object Oriented Programming Python
Yes, OOP is built on two main things inside every class:
Part | Also Called | What it Represents | Example |
---|---|---|---|
Attributes | Properties / Data | What the object has | name , age , balance |
Methods | Functions / Behavior | What the object does | deposit() , greet() |
✅ Real-Life Analogy: Dog
class Dog:
def __init__(self, name, breed): # Attributes
self.name = name
self.breed = breed
def bark(self): # Method
print(f"{self.name} says Woof!")
- Attributes:
name
,breed
→ what the dog has - Method:
bark()
→ what the dog does
🧱 Evergreen OOP Structure (Starter Formula)
Let me give you the evergreen, basic structure of OOP in Python — the one you can always follow when creating your own classes.
class ClassName: def __init__(self, attribute1, attribute2): self.attribute1 = attribute1 self.attribute2 = attribute2 def method1(self): # Do something using self.attribute1 or self.attribute2 pass def method2(self): # Another action pass # Creating object obj = ClassName(value1, value2) # Using method obj.method1()
🧪 Example: Student Class
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def show_info(self):
print(f"Name: {self.name}, Grade: {self.grade}")
# Create object
s1 = Student("Aman", "A")
# Use method
s1.show_info()
🧠 Always Remember This Pattern:
class
→ defines the blueprint__init__()
→ constructor to set up dataself
→ refers to the current objectmethods
→ define behavior/actionsobject
→ actual item created from class
🔑 What is self
in Python OOP?
🧠 Simple Definition:
self
is like saying “me” inside the object — it lets the object refer to its own data and actions.
🚘 Car Example:
Imagine you have 2 cars: one red, one black.
Each car needs to know its own color.
class Car:
def __init__(self, color):
self.color = color # "self" means: this car's color
def show_color(self):
print(f"My color is {self.color}")
self.color
means “this car’s color”- So, if
car1
is red andcar2
is black, each one knows its own color
👨💼 Friend Example:
class Friend:
def __init__(self, name):
self.name = name # "self" refers to this friend
def greet(self):
print(f"Hi! I am {self.name}")
- When you create 2 friends (
Anu
,Vivek
),self.name
ensures each friend introduces themselves correctly.
📚 Book Example:
class Book:
def __init__(self, title):
self.title = title # "self" refers to the specific book
def read(self):
print(f"You're reading '{self.title}'")
- If one book is “Harry Potter” and another is “Atomic Habits”,
self.title
keeps the correct identity for each book.
🔁 Analogy:
Think of self as the word “I” or “my” that each object uses to talk about itself.
A book says: “I am Atomic Habits”
A friend says: “I am Rahul”
A car says: “My color is red”
They say this using self
.
🔹 Is self
a default value?
Yes ✅ — self
is the conventionally used name, but you can technically rename it to anything (like this
, abc
, etc.).
However, you should not change it — because:
self
is the Python standard- Everyone understands it
- It keeps your code readable and maintainable
🛑 So: Don’t change it, even though Python allows it.
❗ What happens if you don’t use self
?
➤ 1. You can’t access attributes
If you skip self
, Python won’t know you’re referring to the object’s own data.
class Car:
def __init__(color): # ❌ No self
color = color
def show_color():
print(color) # ❌ Will give NameError
Output:
NameError: name 'color' is not defined
Python thinks color
is a local variable, not an attribute of the object.
➤ 2. You’ll get TypeError
If you try to define a method without self
, calling it will throw an error:
class Car:
def start_engine(): # ❌ missing self
print("Engine started")
my_car = Car()
my_car.start_engine() # ❌ ERROR
Output:
TypeError: start_engine() takes 0 positional arguments but 1 was given
Because when you do my_car.start_engine()
, Python automatically passes the object as the first argument — and it expects that to be self
.
✅ Summary:
Situation | Result |
---|---|
Don’t use self in attributes | Can’t access object data |
Don’t use self in methods | TypeError: takes 0 but 1 given |
Rename self to something else | Technically works but not recommended |
🔍 What is __str__()
In Object Oriented Programming Python?
🔹 Definition:
__str__()
is a special method in Python used to define what should be printed when you do:
print(object_name)
🧠 Why use it?
Without __str__()
, printing an object gives you this:
<__main__.Car object at 0x000001>
But with __str__()
, you get a nice readable message instead.
💬 Analogy:
Imagine someone asks:
“Tell me about your friend.”
You wouldn’t say:
“Object located at 0xGHF003X”
Instead, you’d say:
“His name is Rahul, he’s 25 years old.”
That’s exactly what __str__()
does — it returns a friendly summary of the object.
✅ Syntax:
def __str__(self):
return "Custom string here"
✅ Evergreen Template for __str__()
class ClassName: def __init__(self, attr1, attr2): self.attr1 = attr1 self.attr2 = attr2 def __str__(self): return f"Summary of {self.attr1}, {self.attr2}"
Use this in every class to make printed objects readable.
🆚 __str__()
vs Custom Methods
Feature | __str__() | Custom Methods (start_engine() , greet() , etc.) |
---|---|---|
Purpose | Used to define how the object looks when printed | Used to define custom behaviors or actions |
Triggers | Automatically called when using print(object) | Manually called like object.method() |
Return | Always returns a string | May return something or just print something |
Typical Use | To summarize object data in 1 line | To perform actions like calculations, greetings, etc. |
Built-in? | Yes, special built-in method | No, you define as per your need |
✅ When to Use __str__()
Use it when:
- You want to print the object and see a clean summary.
- You want easy debugging.
- You’re teaching or learning — makes object output readable.
🔁 Example: __str__()
for Book
print(my_book)
# Output: Book: 'Atomic Habits' by James Clear
Without __str__()
, this would just give a confusing memory address.
✅ When to Use Custom Methods
Use them when:
- You want the object to do something, not just represent itself.
- Examples:
- Start a car
- Greet a person
- Calculate price
- Update a record
🔁 Example: Custom Method for Car
tiago.start_engine()
# Output: The Black car's engine has started
This is an action, not a summary.
📌 Summary Table
Task | Use | Why |
---|---|---|
Show object info on print() | __str__() | Gives readable summary of object |
Make the object “do” something | Custom method | Performs specific action/behavior |
✅ Example Recap: Combined Structure
You can (and should) combine __str__()
and custom methods in the same class. They work together perfectly but serve different purposes:
class Car:
def __init__(self, color, model, price):
self.color = color
self.model = model
self.price = price
def __str__(self):
return f"Car Model: {self.model}, Color: {self.color}, Price: ₹{self.price}" # Used when you do print(car)
def start_engine(self):
print(f"{self.model}'s engine has started.") # Custom behavior
def stop(self):
print(f"{self.model} has stopped.") # Another custom behavior
You can now do:
tiago = Car("Red", "XE", 560000)
print(tiago) # 🔁 Calls __str__()
tiago.start_engine() # ▶️ Calls your custom method
tiago.stop() # ▶️ Another custom method
💡 Use Cases:
Action | What Happens |
---|---|
print(tiago) | Shows car summary using __str__() |
tiago.start_engine() | Starts engine using your method |
tiago.stop() | Stops the car |
🔐 What is Encapsulation in Object Oriented Programming Python?
Encapsulation means:
Hide the data inside the class, and control how it’s seen (getter) and changed (setter).
And with getter-only, you allow people to see the value, but not change it.
🧠 Analogy:
Think of an ATM machine:
- You see your balance (getter ✅)
- You update your PIN or withdraw cash (setter ✅)
- But you cannot touch the bank database directly (private data ❌)
✅ Why Use Getter Only?
- Protects sensitive or read-only data
- Prevents outside tampering
- Gives control over how data is shown
🎯 Why Use Setter?
- To update private data safely
- To validate new values before accepting
- To keep your program clean and secure
🌱 Evergreen Template for Getter Only
class ClassName: def __init__(self, data): self.__data = data # 🔐 Private variable def get_data(self): return self.__data # ✅ Only getter
🌱 Evergreen Template for Getter & Setter Both
class ClassName: def __init__(self, data): self.__data = data def get_data(self): return self.__data def set_data(self, new_data): if new_data is valid: self.__data = new_data else: print("❌ Invalid value")
🚘 Example: Car Class
class Car:
def __init__(self, model, price):
self.model = model
self.__price = price # 🔐 Private
def get_price(self):
return self.__price # ✅ Getter
car1 = Car("XE", 560000)
print(car1.model) # ✅ OK
print(car1.get_price()) # ✅ OK
print(car1.__price) # ❌ Error: cannot access private variable
🧩 Summary:
Feature | Role |
---|---|
__variable | Private, cannot be touched |
get_...() | Safely shows the value |