Python Object-Oriented Programming (OOP) Basics

Zaheer Ahmad 16 min read min read
Python
Python Object-Oriented Programming (OOP) Basics

Python Object-Oriented Programming (OOP) Basics

Target Keywords: python oop, object oriented programming, classes in python, python classes objects
Difficulty: Intermediate | Word Count: ~4000 words | Website: theiqra.edu.pk


Introduction

If you have been writing Python for a little while — printing output, building loops, or writing simple functions — you have already taken a great first step. But as your projects grow in size and complexity, you will quickly discover that keeping everything organized in one long script becomes a nightmare. That is exactly where Python Object-Oriented Programming (OOP) comes in.

Object-Oriented Programming is a programming paradigm — a way of thinking about and structuring your code — that models real-world entities as objects. Instead of writing a long sequence of instructions, you design classes (blueprints) and create objects (instances of those blueprints), each carrying their own data and behavior.

Think of it this way: imagine you are building a university management system for a college in Lahore. You have students, teachers, courses, and fee records. Without OOP, you would end up with hundreds of disconnected variables and functions. With OOP, you create a Student class that holds a student's name, roll number, and CGPA — and all the logic for managing that student lives neatly inside that class.

Why should Pakistani students learn Python OOP?

The Pakistani tech industry is booming. Companies in Karachi, Lahore, and Islamabad — from fintech startups to enterprise software houses — are hiring Python developers for backend development, data science, machine learning, and automation. Every serious Python job listing expects you to understand OOP. Platforms like Upwork and Fiverr are filled with freelance Python projects where OOP skills command higher pay.

More importantly, frameworks you will use professionally — Django (for web development), Flask, FastAPI, and machine learning libraries like scikit-learn — are built entirely around OOP concepts. If you want to work with these tools confidently, mastering classes and objects is not optional; it is essential.

In this tutorial, you will learn everything from creating your first class to understanding inheritance and encapsulation, all with examples rooted in everyday Pakistani contexts.


Prerequisites

Before diving into Python OOP, make sure you are comfortable with the following:

Python Fundamentals you should already know:

  • Variables and data types (strings, integers, lists, dictionaries)
  • Functions — defining them with def, passing arguments, and returning values
  • Loops (for and while) and conditional statements (if/elif/else)
  • Basic error handling with try/except

If you are still building confidence with functions, check out our tutorial Python Functions Explained for Beginners before continuing here.

Tools you will need:

  • Python 3.8 or higher installed on your computer
  • A code editor — VS Code is highly recommended (free, works on Windows, Mac, and Linux)
  • A basic understanding of how to run a .py file from the terminal or command prompt

You do not need any special packages for this tutorial — everything we cover is built into Python's standard library.


Core Concepts & Explanation

What Is a Class and What Is an Object?

The two most fundamental terms in OOP are class and object. Understanding the difference between them is the key to everything else.

A class is a blueprint or template. It defines what data an entity will hold and what actions it can perform, but it does not represent any specific real thing by itself. Think of a class like a photocopy form from NADRA — it defines fields like Name, CNIC, Address, and Date of Birth, but the blank form is not your record.

An object is a specific instance created from that class. When you fill out that NADRA form with your own details, the completed form is your object. You can fill out thousands of forms from the same template — each one is a unique object, but they all follow the same class structure.

Here is how this looks in Python:

# Defining a class (the blueprint)
class Student:
    pass  # We'll add details soon

# Creating objects (instances) from the class
student1 = Student()
student2 = Student()

print(type(student1))  # Output: <class '__main__.Student'>

In this tiny example, Student is the class, and student1 and student2 are two separate objects. They are independent — changes to one do not affect the other.

The __init__ Method: Giving Objects Their Initial Data

When you create an object, you usually want it to start with some data. The __init__ method (called the constructor) runs automatically every time you create a new object from a class. It sets up the object's initial state.

class Student:
    def __init__(self, name, roll_number, cgpa):
        self.name = name
        self.roll_number = roll_number
        self.cgpa = cgpa

# Creating a student object
student1 = Student("Ahmad Ali", "FAST-2024-101", 3.75)
print(student1.name)         # Output: Ahmad Ali
print(student1.roll_number)  # Output: FAST-2024-101
print(student1.cgpa)         # Output: 3.75

Here, self refers to the object being created. When you write self.name = name, you are saying "this object's name attribute equals the name argument I passed in." The self parameter is always the first parameter of any method inside a class — Python passes it automatically when you call the method.

The Four Pillars of OOP

Python OOP rests on four core principles. Understanding these will make you a dramatically better programmer.

1. Encapsulation

Encapsulation means bundling data (attributes) and the methods that work on that data together inside one class, and controlling access to that data from the outside world. This protects your object's internal state from accidental misuse.

In Python, we indicate that an attribute is private (not meant to be accessed directly from outside the class) by prefixing it with a double underscore __:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance  # Private attribute

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"Rs. {amount} deposited. New balance: Rs. {self.__balance}")

    def get_balance(self):
        return self.__balance

# Usage
account = BankAccount("Fatima Malik", 50000)
account.deposit(10000)           # Works fine
print(account.get_balance())     # Works: Rs. 60000
# print(account.__balance)       # Would raise AttributeError — protected!

Encapsulation ensures that nobody can directly set account.__balance = 1000000 from outside the class — they have to go through the controlled deposit method.

2. Inheritance

Inheritance allows a new class (called the child class or subclass) to automatically receive all the attributes and methods of an existing class (called the parent class or superclass). This promotes code reuse and a logical hierarchy.

Imagine you have a general Person class. Both Student and Teacher share common properties like name, age, and CNIC. Instead of writing those twice, you make Student and Teacher inherit from Person:

class Person:
    def __init__(self, name, age, cnic):
        self.name = name
        self.age = age
        self.cnic = cnic

    def introduce(self):
        print(f"My name is {self.name} and I am {self.age} years old.")

class Student(Person):  # Student inherits from Person
    def __init__(self, name, age, cnic, roll_number):
        super().__init__(name, age, cnic)  # Call parent's __init__
        self.roll_number = roll_number

    def study(self):
        print(f"{self.name} (Roll: {self.roll_number}) is studying.")

class Teacher(Person):  # Teacher also inherits from Person
    def __init__(self, name, age, cnic, subject):
        super().__init__(name, age, cnic)
        self.subject = subject

    def teach(self):
        print(f"Professor {self.name} is teaching {self.subject}.")

super().__init__(...) calls the parent class's constructor, so you do not have to rewrite the name, age, and cnic setup.

3. Polymorphism

Polymorphism means "many forms." It allows objects of different classes to be treated through the same interface, with each class implementing that interface in its own way.

class Shape:
    def area(self):
        pass  # To be implemented by subclasses

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
    print(f"Area: {shape.area()}")  # Same method call, different behavior

Both Circle and Rectangle have an area() method, but each calculates it differently. You can call shape.area() without knowing or caring which specific shape you are dealing with.

4. Abstraction

Abstraction means hiding complex implementation details and exposing only what is necessary. When you call account.deposit(5000), you do not need to know how the balance is updated internally — you just know what the method does. The complexity is hidden behind a simple interface.


Practical Code Examples

Example 1: University Fee Management System

This example builds a practical fee management system relevant to Pakistani university students. It demonstrates classes, objects, methods, and encapsulation working together.

class FeeRecord:
    """Manages fee records for a university student."""

    def __init__(self, student_name, roll_number, semester_fee):
        # Initialize student's fee record
        self.student_name = student_name
        self.roll_number = roll_number
        self.__semester_fee = semester_fee   # Total fee for the semester (private)
        self.__amount_paid = 0               # Amount paid so far (private)

    def pay_fee(self, amount):
        """Process a fee payment."""
        if amount <= 0:
            print("Error: Payment amount must be positive.")
            return

        remaining = self.__semester_fee - self.__amount_paid

        if amount > remaining:
            print(f"Error: You are trying to pay Rs. {amount} but only Rs. {remaining} is due.")
            return

        self.__amount_paid += amount
        print(f"Payment of Rs. {amount:,} received for {self.student_name}.")
        print(f"  Total paid: Rs. {self.__amount_paid:,}")
        print(f"  Remaining:  Rs. {self.get_remaining_fee():,}")

    def get_remaining_fee(self):
        """Returns the remaining unpaid fee."""
        return self.__semester_fee - self.__amount_paid

    def get_status(self):
        """Returns whether the student's fee is fully cleared."""
        if self.__amount_paid >= self.__semester_fee:
            return "CLEARED"
        else:
            return f"PENDING (Rs. {self.get_remaining_fee():,} due)"

    def display_summary(self):
        """Prints a full fee summary."""
        print(f"\n--- Fee Summary ---")
        print(f"Student:       {self.student_name}")
        print(f"Roll Number:   {self.roll_number}")
        print(f"Semester Fee:  Rs. {self.__semester_fee:,}")
        print(f"Amount Paid:   Rs. {self.__amount_paid:,}")
        print(f"Status:        {self.get_status()}")
        print(f"-------------------")


# --- Using the FeeRecord class ---

# Create a fee record for a student in Lahore
ali = FeeRecord("Ali Hassan", "UET-2024-055", 85000)

ali.display_summary()   # View initial state

ali.pay_fee(50000)      # First installment
ali.pay_fee(35000)      # Second installment (clears the fee)

ali.display_summary()   # Final state

Line-by-line explanation:

  • class FeeRecord: — Defines the class. The docstring in triple quotes describes its purpose.
  • def __init__(self, ...) — The constructor. Sets up every new fee record with a student's name, roll number, and total semester fee.
  • self.__semester_fee and self.__amount_paid — Private attributes (double underscore). They cannot be accessed directly from outside the class, protecting the data integrity.
  • def pay_fee(self, amount): — A method that updates __amount_paid. It validates the input before making any changes, ensuring no one can accidentally overpay or pay zero.
  • self.__amount_paid += amount — Updates the private balance inside the class safely.
  • {amount:,} — Python's format specifier that adds commas to large numbers (e.g., 85000 becomes 85,000), which is standard in Pakistani financial contexts.
  • ali = FeeRecord(...) — Creates an object from the class with real data.

Expected Output:

--- Fee Summary ---
Student:       Ali Hassan
Roll Number:   UET-2024-055
Semester Fee:  Rs. 85,000
Amount Paid:   Rs. 0
Status:        PENDING (Rs. 85,000 due)
-------------------
Payment of Rs. 50,000 received for Ali Hassan.
  Total paid: Rs. 50,000
  Remaining:  Rs. 35,000
Payment of Rs. 35,000 received for Ali Hassan.
  Total paid: Rs. 85,000
  Remaining:  Rs. 0
--- Fee Summary ---
Student:       Ali Hassan
Roll Number:   UET-2024-055
Semester Fee:  Rs. 85,000
Amount Paid:   Rs. 85,000
Status:        CLEARED
-------------------

Example 2: Real-World Application — E-Commerce Product Catalog

This example models a simple product catalog for a Pakistani online store — a scenario directly relevant to freelancers building e-commerce sites for local businesses. It demonstrates inheritance and polymorphism together.

class Product:
    """Base class for all products in the store."""

    def __init__(self, name, price_pkr, stock):
        self.name = name
        self.price_pkr = price_pkr
        self.__stock = stock

    def add_stock(self, quantity):
        """Add new inventory."""
        self.__stock += quantity
        print(f"Restocked {self.name}. New stock: {self.__stock} units.")

    def sell(self, quantity):
        """Process a sale."""
        if quantity > self.__stock:
            print(f"Sorry! Only {self.__stock} units of {self.name} available.")
            return False
        self.__stock -= quantity
        print(f"Sold {quantity}x {self.name}. Remaining stock: {self.__stock}.")
        return True

    def get_stock(self):
        return self.__stock

    def display_info(self):
        """Display product info — can be overridden by subclasses."""
        print(f"Product: {self.name} | Price: Rs. {self.price_pkr:,} | Stock: {self.__stock}")


class ClothingItem(Product):
    """A clothing product with size and fabric details."""

    def __init__(self, name, price_pkr, stock, size, fabric):
        super().__init__(name, price_pkr, stock)  # Initialize parent attributes
        self.size = size
        self.fabric = fabric

    def display_info(self):
        """Override parent method to include clothing-specific details."""
        print(f"[CLOTHING] {self.name} | Size: {self.size} | Fabric: {self.fabric} "
              f"| Price: Rs. {self.price_pkr:,} | Stock: {self.get_stock()}")


class ElectronicItem(Product):
    """An electronic product with warranty details."""

    def __init__(self, name, price_pkr, stock, brand, warranty_months):
        super().__init__(name, price_pkr, stock)
        self.brand = brand
        self.warranty_months = warranty_months

    def display_info(self):
        """Override parent method to include electronics-specific details."""
        print(f"[ELECTRONICS] {self.name} by {self.brand} "
              f"| Warranty: {self.warranty_months} months "
              f"| Price: Rs. {self.price_pkr:,} | Stock: {self.get_stock()}")


# --- Build the store catalog ---
catalog = [
    ClothingItem("Khaddar Shalwar Kameez", 3500, 50, "L", "Khaddar"),
    ClothingItem("Lawn Suit", 4200, 30, "M", "Lawn"),
    ElectronicItem("Samsung Galaxy A55", 89000, 15, "Samsung", 12),
    ElectronicItem("JBL Bluetooth Speaker", 12500, 25, "JBL", 6),
]

print("=== Store Catalog ===")
for product in catalog:
    product.display_info()   # Polymorphism: each class handles this its own way

print("\n=== Processing Sales ===")
catalog[0].sell(5)   # Sell 5 Khaddar suits
catalog[2].sell(20)  # Try to sell 20 Samsung phones (only 15 in stock)
catalog[2].sell(3)   # Sell 3 Samsung phones (works now)

Key OOP concepts demonstrated:

  • Inheritance: ClothingItem and ElectronicItem both inherit from Product. They get sell(), add_stock(), and get_stock() for free.
  • Method Overriding (Polymorphism): Each subclass defines its own display_info() method. When you loop through the catalog and call product.display_info(), Python automatically calls the right version depending on the object's actual type.
  • Encapsulation: __stock is private to Product. Subclasses cannot directly modify it — they must use sell() and add_stock().
  • super().__init__(...): Called in each subclass constructor to ensure the parent class sets up its own attributes before the subclass adds its own.

Common Mistakes & How to Avoid Them

Mistake 1: Forgetting self in Method Parameters or Attribute Access

This is the single most common mistake beginners make with Python classes. Every method inside a class must have self as its first parameter, and every attribute must be accessed through self.

Wrong code:

class Student:
    def __init__(name, roll_number):   # Missing 'self'!
        name = name                    # This does nothing useful
        roll_number = roll_number

    def greet():                       # Missing 'self'!
        print(f"Hello, {name}")        # 'name' is not defined here

s = Student("Fatima", "KU-001")
s.greet()
# TypeError: Student.__init__() takes 2 positional arguments but 3 were given

Correct code:

class Student:
    def __init__(self, name, roll_number):   # 'self' is first
        self.name = name                     # Assign to self
        self.roll_number = roll_number

    def greet(self):                         # 'self' is first
        print(f"Hello, my name is {self.name}")

s = Student("Fatima", "KU-001")
s.greet()  # Hello, my name is Fatima

Why this happens: Python automatically passes the object as the first argument to every instance method. If you forget self, Python tries to use your first real argument as self, causing confusing errors. Always make self the very first parameter in every method definition.

Mistake 2: Accidentally Overwriting Class Attributes with Instance Attributes

Python has two types of attributes: class attributes (shared among all instances) and instance attributes (unique to each object). Confusing them can cause subtle bugs.

Wrong code:

class BankAccount:
    bank_name = "HBL"      # Class attribute — shared by ALL accounts
    total_accounts = 0

    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
        total_accounts += 1  # Bug! This tries to modify a local variable, not the class attribute

acc = BankAccount("Ahmad", 10000)
# UnboundLocalError: local variable 'total_accounts' referenced before assignment

Correct code:

class BankAccount:
    bank_name = "HBL"
    total_accounts = 0

    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
        BankAccount.total_accounts += 1   # Use ClassName.attribute to modify class attributes

acc1 = BankAccount("Ahmad", 10000)
acc2 = BankAccount("Fatima", 25000)

print(BankAccount.total_accounts)  # 2
print(BankAccount.bank_name)       # HBL
print(acc1.bank_name)              # HBL — all instances share this

The rule: To modify a class attribute from inside a method, always reference it as ClassName.attribute, not self.attribute or the bare name. Using self.attribute would create a new instance attribute that shadows the class attribute, which is usually not what you want.

Mistake 3: Not Calling super().__init__() in Inherited Classes

When a child class defines its own __init__, it overrides the parent's constructor entirely. If you do not call super().__init__(), the parent's attributes never get set up, leading to AttributeError errors.

Wrong code:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Teacher(Person):
    def __init__(self, subject):  # Forgot to include name and age, and forgot super()
        self.subject = subject

t = Teacher("Mathematics")
print(t.name)  # AttributeError: 'Teacher' object has no attribute 'name'

Correct code:

class Teacher(Person):
    def __init__(self, name, age, subject):
        super().__init__(name, age)  # Let Person set up name and age
        self.subject = subject       # Then add Teacher-specific attributes

t = Teacher("Ustad Mahmood", 45, "Mathematics")
print(t.name)     # Ustad Mahmood
print(t.subject)  # Mathematics

Practice Exercises

Exercise 1: Build a Library Book Management System

Problem: A library in Islamabad needs a system to manage books. Create a Book class with the following requirements:

  • Attributes: title, author, isbn, and a private __is_available attribute (default True)
  • Method checkout(borrower_name): Marks the book as unavailable and prints who borrowed it. Should show an error if the book is already checked out.
  • Method return_book(): Marks the book as available again.
  • Method get_status(): Returns a string — either "Available" or "Checked out".
  • Method display(): Prints full book info including availability status.

Test it by creating two books, checking one out, returning it, and displaying both.

Solution:

class Book:
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn
        self.__is_available = True
        self.__current_borrower = None

    def checkout(self, borrower_name):
        if not self.__is_available:
            print(f"Sorry! '{self.title}' is currently with {self.__current_borrower}.")
            return False
        self.__is_available = False
        self.__current_borrower = borrower_name
        print(f"'{self.title}' has been checked out by {borrower_name}.")
        return True

    def return_book(self):
        if self.__is_available:
            print(f"'{self.title}' was not checked out.")
            return
        print(f"'{self.title}' returned by {self.__current_borrower}. Thank you!")
        self.__is_available = True
        self.__current_borrower = None

    def get_status(self):
        if self.__is_available:
            return "Available"
        return f"Checked out by {self.__current_borrower}"

    def display(self):
        print(f"\nTitle:   {self.title}")
        print(f"Author:  {self.author}")
        print(f"ISBN:    {self.isbn}")
        print(f"Status:  {self.get_status()}")


# Testing
book1 = Book("Urdu Adab ki Tarikh", "Dr. Anwar Sadeed", "978-969-416-001-2")
book2 = Book("Introduction to Python", "Mark Lutz", "978-1-491-94600-3")

book1.display()
book2.display()

book1.checkout("Zara Hussain")
book1.checkout("Omar Sheikh")   # Should show error
book1.display()

book1.return_book()
book1.display()

Exercise 2: Pakistani City Temperature Tracker

Problem: Build a CityWeather class for a weather app that tracks temperature readings for Pakistani cities. Requirements:

  • Constructor accepts city_name
  • Method add_reading(temp_celsius): Adds a temperature reading to a list
  • Method get_average(): Returns the average temperature (return None if no readings)
  • Method get_highest() and get_lowest(): Return the highest and lowest recorded temperatures
  • Method summary(): Prints a formatted weather summary

Test with at least two cities (e.g., Karachi and Islamabad) and multiple readings.

Solution:

class CityWeather:
    def __init__(self, city_name):
        self.city_name = city_name
        self.__readings = []   # Private list of temperature readings

    def add_reading(self, temp_celsius):
        self.__readings.append(temp_celsius)
        print(f"Reading added for {self.city_name}: {temp_celsius}°C")

    def get_average(self):
        if not self.__readings:
            return None
        return sum(self.__readings) / len(self.__readings)

    def get_highest(self):
        if not self.__readings:
            return None
        return max(self.__readings)

    def get_lowest(self):
        if not self.__readings:
            return None
        return min(self.__readings)

    def summary(self):
        if not self.__readings:
            print(f"\nNo readings available for {self.city_name}.")
            return
        print(f"\n=== Weather Summary: {self.city_name} ===")
        print(f"  Readings:   {self.__readings}")
        print(f"  Average:    {self.get_average():.1f}°C")
        print(f"  Highest:    {self.get_highest()}°C")
        print(f"  Lowest:     {self.get_lowest()}°C")
        print(f"=================={'=' * len(self.city_name)}")


# Testing
karachi = CityWeather("Karachi")
islamabad = CityWeather("Islamabad")

for temp in [33, 35, 31, 36, 34]:
    karachi.add_reading(temp)

for temp in [22, 18, 25, 20, 17]:
    islamabad.add_reading(temp)

karachi.summary()
islamabad.summary()

Frequently Asked Questions

What is the difference between a class and an object in Python?

A class is a blueprint or template that defines the structure and behavior of a type of entity — it is the design, not the thing itself. An object is a concrete instance created from that class, with its own specific data. For example, Student is a class, while student1 = Student("Ahmad", "001", 3.8) creates a specific object (instance) of that class. You can create as many objects as you need from a single class, and each one is independent.

What does self mean in Python classes?

self refers to the current object (instance) that a method is being called on. When you write self.name = name inside __init__, you are storing the name value as an attribute belonging specifically to this object, not to the class in general. Every instance method must have self as its first parameter, but you do not pass it manually — Python automatically provides it when you call the method on an object (e.g., student1.greet() automatically passes student1 as self).

What is the purpose of __init__ in Python OOP?

__init__ is the constructor method. It runs automatically every time you create a new object from a class. Its main job is to initialize the object's attributes — setting up the object's initial state with the values you pass when creating it. Without __init__, your objects would start empty and you would have to manually set every attribute after creation, which is both inconvenient and error-prone.

How does inheritance work in Python, and when should I use it?

Inheritance allows a child class to automatically receive all the attributes and methods of a parent class, using the syntax class ChildClass(ParentClass):. Use inheritance when two or more classes share common behavior and represent an "is-a" relationship — for example, a Teacher is a Person, and a Car is a Vehicle. Inheritance reduces code duplication, makes your code easier to maintain, and creates a logical, real-world-inspired hierarchy. Always call super().__init__() in the child class constructor to ensure the parent's initialization also runs.

What is the difference between public, protected, and private attributes in Python?

In Python, access control is indicated by naming convention rather than strict enforcement. A public attribute (e.g., self.name) can be accessed freely from anywhere. A protected attribute (single underscore, e.g., self._name) signals to other developers "please do not access this directly from outside the class or its subclasses," though Python does not prevent it. A private attribute (double underscore, e.g., self.__name) triggers Python's name mangling, making it harder (though not impossible) to access from outside the class. Use private attributes for sensitive data like passwords or financial balances, and protected attributes for internal implementation details meant only for subclasses.


Summary & Key Takeaways

After working through this tutorial, here are the essential points to remember:

  • Classes are blueprints, objects are instances. A class defines the structure; objects are the actual, usable entities created from that structure using ClassName(arguments).
  • __init__ sets up every new object. It runs automatically on creation and uses self.attribute = value to store data on the specific object being created.
  • self always refers to the current object and must be the first parameter in every instance method — Python passes it automatically, so you never include it when calling a method.
  • The four pillars of OOP are Encapsulation, Inheritance, Polymorphism, and Abstraction. Each addresses a different aspect of writing clean, reusable, and maintainable code.
  • Inheritance (class Child(Parent):) promotes code reuse — always call super().__init__() in a child class constructor to properly initialize the parent's attributes.
  • Private attributes (self.__attr) protect sensitive data by preventing direct external access and forcing interactions through controlled methods.

You have taken a solid step into object-oriented programming — well done! To continue building your Python OOP skills, explore these related tutorials on theiqra.edu.pk:

  • Python Special (Dunder) Methods Explained — Learn how to make your objects work with Python's built-in operators. For example, define __str__ so print(student) shows something meaningful, or __len__ so len(your_object) works naturally.
  • Python Decorators and @property — Discover how to use @property to create smart getters and setters that give your private attributes a cleaner public interface, and how decorators like @staticmethod and @classmethod change how class methods behave.
  • Django for Beginners — Build Your First Web App — Django is Python's most popular web framework and is built entirely on OOP. Once you are comfortable with classes and objects, Django is your next big step toward building real-world web applications — a highly valuable skill in Pakistan's growing tech industry.
  • Python File Handling and Exception Handling — Learn how to read and write files and handle errors gracefully — skills you will need when building real applications that work with data stored on disk.

Published on theiqra.edu.pk — Pakistan's friendly programming education platform. Questions? Drop them in the comments below!

Practice the code examples from this tutorial
Open Compiler
Share this tutorial:

Test Your Python Knowledge!

Finished reading? Take a quick quiz to see how much you've learned from this tutorial.

Start Python Quiz

About Zaheer Ahmad