Introduction to Object-Oriented Programming (OOP) and its Use in Python

Lesson Overview

This lesson introduces Object-Oriented Programming (OOP) as a programming standard that organizes code by grouping related data and behaviour into objects. It explains why OOP is valuable in Python for building scalable, reusable, and maintainable software, and lays the foundation for understanding core OOP concepts.

Lesson Content

Student Portfolio

Let’s begin with a simple example: a student's portfolio or bio-data. What kind of information do you think should be captured for each student? Usually, it includes details like name, age, class, and roll number.

How would you represent this student data in a python programming? A general approach is to use dictionaries where each key represents a field (like "name") and the value holds the data ("Amit").

student1 = {"name": "Krishna", "age": 16, "class": "10th", "roll_number": 21, "type" : 'student' }
student2 = {"name": "Radha", "age": 15, "class": "10th", "roll_number": 22, "type" : 'student' }

This works well for small sets of data. But what if the school size grows and you have hundreds or thousands of students? Your program will now have many dictionaries, and managing all this data becomes tedious.

Adding New Keys

Suppose the school wants to add new fields such as total fees or concessions. You now have to go through every single dictionary and add these new keys. you might write functions to achieve the same objective

def add_fees_info(student):         # 'student' is of dict type
    student["total_fees"] = 50000
    student["concessions"] = 5000
student1 = add_fees_info(student1 ) # This will add the two new keys to the existing dictionary
#Output : {"name": "Krishna", "age": 16, "class": "10th", "roll_number": 21, "type" : 'student', "total_fees" : 50000, "concessions" : 5000 }

But as the program grows, remembering to call this function on every student dictionary and this may lead to mistakes, inconsistent data, and increasing complexity.

Adding Behaviour - The Full Name Example

Imagine you want to display the full name of the student but with a prefix, such as "Student : ". You can write a function:

def full_name(student):                      # 'student' is of dict type
    return 'Student : '+ student["name"] 

So far so good. Now imagine the school needs a similar portfolio for teachers. For teachers, a similar approach can be followed by creating another dictionary containing similar but possibly different fields such as name, age, subject, and experience. This reflects their unique data needs while maintaining a similar structure to student data.

teacher1 = {"name": "Prasad", "age": 35, "subject": "Socail", "id": 2122, "type" : 'teacher' }
teacher2 = {"name": "Venkat", "age": 42, "subject": "Maths", "id": 2234, "type" : 'teacher' }

For teachers, the full name prefix should be "Teacher : ". If you reuse the same function, you might need to add an if-else condition inside:

def full_name(person):                             # 'person' is a dictionary that represents either stundent or teacher
    if person['type'] == 'teacher' :
        return "Teacher : " + person["name"] 
    else:
        return "Student : " + person["name"] 

Where Does Complexity Hide?

Although this approach solves the immediate problem, the function quickly becomes cluttered and complicated as more roles like parents, staff, or administrators are added, each with their own behaviours. This forces repeated modifications to existing, working code, increasing the chance of introducing errors and reducing code clarity. Every time a new role is introduced, the same function requires editing, making the program tightly coupled, harder to maintain, and more bug-prone.

The complexity of storing, updating, and retrieving data with functions and conditionals grows significantly as the data and roles increase, even when using complex types like dictionaries combined with if-else logic. The underlying issue remains: tightly coupled code with low modularity and poor scalability. To tackle these challenges, programming introduces the concept called Object-Oriented Programming (OOP)

Object-Oriented Programming (OOP)

OOP helps solve this by modelling real-world entities as objects that bundle both data and functions together. Instead of separated data (like dictionaries) and separate functions, each entity (student, teacher, parent) is represented as a specific class.

Thinking of real-world entities as objects helps by simulating how things exist and behave in reality. For example, an object can be a building, a person, or any physical entity with distinct features like

  • attributes : (like colour, height, name)
  • behaviours : (like opening a door, walking, or speaking)

Viewing these entities as objects allows us to club together related data (attributes) and the functions (behaviours) that act on this data into a single unit

This bundling creates a more natural, intuitive way to model complex systems, reducing code duplication and making programs easier to understand and maintain. This approach is similar to how we see the world: each object keeps track of its own data and knows what it can do and what it cannot do. Because of this, programs become easier to scale and organize into independent, manageable parts.

Interesting Note: OOPS is just a programming concept and can be implemented in other programming languages also. for example C++ extends C by including OOP features

Prerequisite: A basic understanding of Python is required to continue with this course. If you are new to Python, please start with our ByLearning Python course.

Quick Recap

We have understood why OOP is useful and how it helps organize code by modelling real-world entities as objects.

What’s Next?

Next, we will dive into the fundamental building blocks of OOP in Python: Classes, Objects, and Instances. These concepts form the foundation of writing object-oriented programs.