Python OOP – Deeper Concepts Explained with Examples
Introduction: Going Beyond the Basics of Python OOP
Have you ever wondered how Python can be used to create real-world applications such as banking systems, school management software, online shopping websites, and games?
One important answer is Object-Oriented Programming (OOP).
In our earlier Python tutorials, we learned about classes, objects, inheritance, encapsulation, and polymorphism. These concepts help us organise programs and reuse code.
But Python OOP has several more useful concepts that help us write cleaner, more reusable, and maintainable programs.
In this tutorial, we will learn:
Constructors and
__init__()The meaning of
selfInstance variables and class variables
Instance methods
Class methods
Static methods
Method overriding
The
super()functionAbstract classes
Composition
Association
Aggregation
We will understand each concept using simple explanations, Python code, and real-world examples.
1. Constructors in Python
What is a constructor?
A constructor helps initialise an object when it is created.
In Python, the special method __init__() is commonly called the constructor, although technically it is the initialisation method. Python creates the object first using __new__(), and then calls __init__() to initialise it.
Consider a school application that stores student information. Whenever we create a new student, we want to assign a name and class.
Without initialisation, we might have to assign these values separately after creating the object.
Example: Using __init__()
class Student:
def __init__(self, name, class_name):
self.name = name
self.class_name = class_name
def display(self):
print("Name:", self.name)
print("Class:", self.class_name)
s1 = Student("Rahul", "9")
s1.display()Output:
Name: Rahul
Class: 9How does it work?
class Student:defines a class.__init__()runs automatically when a new object is initialised.nameandclass_nameare values supplied when the object is created.self.nameandself.class_namestore those values in the object.s1.display()displays the stored information.
Important: Unlike some other programming languages, Python does not support multiple constructors simply by defining several __init__() methods in the same class. The last definition replaces earlier definitions. Optional arguments and class methods are common ways to support alternative creation patterns.
2. Understanding self in Python
What is self?
self refers to the current object on which an instance method is operating.
Imagine a classroom with 30 students. Every student has a name, but each student has a different name. Python needs a way to know which student's information is being accessed.
That is where self helps.
Example
class Student:
def __init__(self, name):
self.name = name
def introduce(self):
print("My name is", self.name)
s1 = Student("Aman")
s2 = Student("Priya")
s1.introduce()
s2.introduce()Output:
My name is Aman
My name is PriyaWhen s1.introduce() is called, self refers to s1.
When s2.introduce() is called, self refers to s2.
Therefore, the same method can work with different objects.
Is self a Python keyword?
No. self is a naming convention, not a reserved keyword. However, Python programmers almost always use this name for the first parameter of an instance method.
You normally do not pass it explicitly. Python supplies the object automatically when you call a method through an instance.
3. Instance Variables vs Class Variables
Variables in a class can belong to individual objects or be shared by all objects of that class.
Understanding this difference is essential for writing correct OOP programs.
Instance variables
Instance variables store information specific to an individual object.
For example, each student has a different name and roll number.
Class variables
Class variables are defined directly inside the class, outside its instance methods. They are shared through the class unless an object creates its own attribute with the same name.
For example, all students in a particular school might share the same school name.
Example
class Student:
school = "ABC Public School" # Class variable
def __init__(self, name, roll_no):
self.name = name # Instance variable
self.roll_no = roll_no # Instance variable
s1 = Student("Aman", 101)
s2 = Student("Priya", 102)
print(s1.name)
print(s2.name)
print(s1.school)
print(s2.school)
print(Student.school)Output:
Aman
Priya
ABC Public School
ABC Public School
ABC Public SchoolDifference between instance and class variables
| Feature | Instance variable | Class variable |
|---|---|---|
| Belongs to | An individual object | The class |
| Typical definition | self.name | school = "ABC" |
| Values | Can differ between objects | Shared through the class |
| Access | s1.name | Student.school |
Important point
If you write:
s1.school = "XYZ School"you create or update an attribute on s1; you do not change Student.school or the value seen through other objects that have not overridden the attribute.
To change the shared class variable, use:
Student.school = "XYZ School"Use class variables for genuinely shared information, not for mutable data that should be independent for each object.
4. Instance Methods
An instance method operates on a particular object. Its first parameter is conventionally named self.
Instance methods can access or modify instance variables and can also use class-level information.
Example: A Bank Account
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def show_balance(self):
print("Account holder:", self.owner)
print("Balance:", self.balance)
account = BankAccount("Rahul", 5000)
account.deposit(1500)
account.show_balance()Output:
Account holder: Rahul
Balance: 6500Explanation
deposit()modifies the balance of the particular account.show_balance()displays information about that account.Both methods use
selfto access the object's attributes.
If another bank account is created, its balance remains independent.
When should you use instance methods?
Use them whenever an operation depends on the data of a specific object, such as withdrawing money from an account, calculating a student's marks, or moving a game character.
5. Class Methods
A class method works with the class rather than a particular object's state.
It uses the @classmethod decorator, and its first parameter is conventionally named cls. This parameter refers to the class on which the method is called.
Example
class Student:
school = "ABC Public School"
def __init__(self, name):
self.name = name
@classmethod
def change_school(cls, new_school):
cls.school = new_school
s1 = Student("Aman")
s2 = Student("Priya")
Student.change_school("Delhi Public School")
print(s1.school)
print(s2.school)Output:
Delhi Public School
Delhi Public SchoolExplanation
The method change_school() changes the shared class variable.
The cls parameter refers to the class, so cls.school accesses the class attribute.
A useful application: Alternative object creation
Class methods can also create objects using alternative input formats.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_string(cls, data):
name, age = data.split(",")
return cls(name, int(age))
s1 = Student.from_string("Aman,15")
print(s1.name)
print(s1.age)Output:
Aman
15Here, from_string() acts as an alternative way to create a student object.
When should you use class methods?
Use them to work with class-level data or provide alternative ways of creating objects.
6. Static Methods
A static method is a function placed inside a class because it is logically related to that class.
It uses the @staticmethod decorator.
Unlike instance methods and class methods, a static method does not automatically receive self or cls.
Example: Checking Eligibility
class Student:
@staticmethod
def is_eligible(age):
return age >= 14
print(Student.is_eligible(15))
print(Student.is_eligible(12))Output:
True
FalseExplanation
The method checks whether an age meets a condition.
It does not need to access any student object or class variable. Therefore, a static method is suitable.
Instance method vs class method vs static method
| Feature | Instance method | Class method | Static method |
|---|---|---|---|
| Decorator | None required | @classmethod | @staticmethod |
| First parameter | self | cls | No automatic parameter |
| Main purpose | Work with an object | Work with the class | Related utility operation |
| Access to object state | Through self | Not automatically | Not automatically |
Remember this simple rule:
Instance method: Works with an object.
Class method: Works with the class.
Static method: Performs a related operation without automatically receiving the object or class.
7. Method Overriding
Method overriding occurs when a child class provides its own implementation of a method inherited from its parent class.
It is an important part of runtime polymorphism.
Real-world example
Consider a school application that calculates the way different types of students receive awards.
The basic student class can have a method called award(). A sports student and an academic student can override that method to provide different behaviour.
Example
class Student:
def award(self):
print("Student receives a certificate.")
class SportsStudent(Student):
def award(self):
print("Student receives a sports trophy.")
class AcademicStudent(Student):
def award(self):
print("Student receives an academic medal.")
s1 = SportsStudent()
s2 = AcademicStudent()
s1.award()
s2.award()Output:
Student receives a sports trophy.
Student receives an academic medal.Explanation
Both child classes inherit from Student, but each provides its own version of award().
When the method is called, Python uses the implementation appropriate to the object's class.
Why is method overriding useful?
It allows related classes to share a common interface while implementing behaviour differently.
You can use the same method name for different payment methods, vehicles, reports, or employee types.
8. Using super() in Python
The super() function provides access to methods defined in the next class in Python's method resolution order (MRO).
It is commonly used in a child class to call a parent class's implementation.
Example
class Person:
def __init__(self, name):
self.name = name
def display(self):
print("Name:", self.name)
class Student(Person):
def __init__(self, name, roll_no):
super().__init__(name)
self.roll_no = roll_no
def display(self):
super().display()
print("Roll number:", self.roll_no)
s1 = Student("Aman", 101)
s1.display()Output:
Name: Aman
Roll number: 101What happens here?
Studentinherits fromPerson.super().__init__(name)calls the next implementation of__init__()in the method resolution order.The parent's initialisation code sets
self.name.The child class sets
self.roll_no.super().display()calls the inherited implementation before displaying the roll number.
Without super(), you might duplicate code from the parent class.
Does super() always mean "call my parent"?
In simple single inheritance, this description is often sufficient. More precisely, super() follows the method resolution order. This is especially important when working with multiple inheritance.
Best practice: When extending an inherited method, use super() where appropriate rather than unnecessarily repeating the parent class's code.
9. Abstract Classes in Python
An abstract class defines a common structure that its subclasses are expected to implement.
Python supports abstract classes through the abc module.
An abstract class can contain ordinary methods as well as abstract methods. An abstract method declares behaviour that concrete subclasses must provide before they can be instantiated.
Example: Different Payment Methods
Suppose an online shopping application supports card payments and UPI payments.
Both payment types should have a pay() method, but their implementations are different.
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
class CardPayment(Payment):
def pay(self, amount):
print("Paid", amount, "using card")
class UPIPayment(Payment):
def pay(self, amount):
print("Paid", amount, "using UPI")
p1 = CardPayment()
p2 = UPIPayment()
p1.pay(1000)
p2.pay(500)Output:
Paid 1000 using card
Paid 500 using UPIWhat does ABC mean?
ABC stands for Abstract Base Class.
The @abstractmethod decorator marks a method as abstract.
A class that still has unimplemented abstract methods cannot normally be instantiated.
For example:
payment = Payment()This raises a TypeError because Payment has an unimplemented abstract method.
Why are abstract classes useful?
They help enforce a common interface across related classes.
For example, every payment type in an application can be required to implement pay(). This makes the application easier to extend and maintain.
10. Composition in Python
Composition describes a strong whole-part relationship in which one object owns or manages another object's lifecycle as part of its design.
Consider a computer. It contains components such as a processor. A computer object can create and manage its processor object.
Example
class Processor:
def start(self):
print("Processor started")
class Computer:
def __init__(self):
self.processor = Processor()
def start_computer(self):
self.processor.start()
print("Computer started")
computer = Computer()
computer.start_computer()Output:
Processor started
Computer startedExplanation
Processoris a separate class.Computercreates aProcessorobject during initialisation.The computer uses that processor to perform its operation.
The processor is created and managed internally in this example, illustrating a simple form of composition.
When is composition useful?
Composition is useful when a class needs the services of other objects.
Examples include:
A car containing an engine object.
A shopping cart managing cart-item objects.
A computer using processor and memory objects.
Composition helps us build complex applications by combining smaller, reusable classes.
Important: Composition is a design relationship, not a special Python keyword. The exact strength of ownership depends on how the application creates, shares, and manages its objects.
11. Association in Python
Association is a relationship between two objects that interact with each other.
Neither object necessarily owns the other.
Consider a teacher and a student. A teacher teaches a student, but the teacher does not own the student, and both objects can exist independently.
Example
class Teacher:
def __init__(self, name):
self.name = name
def teach(self, student):
print(self.name, "is teaching", student.name)
class Student:
def __init__(self, name):
self.name = name
teacher = Teacher("Mr Sharma")
student = Student("Aman")
teacher.teach(student)Output:
Mr Sharma is teaching AmanExplanation
The teach() method receives a Student object as an argument.
The teacher interacts with the student, but the teacher does not create or own that student.
Both objects are created independently.
Where is association used?
A doctor treating a patient.
A teacher teaching a student.
A customer interacting with a bank employee.
A driver driving a vehicle.
Association represents interaction or a connection between objects.
12. Aggregation in Python
Aggregation is a specialised form of association that represents a whole-part relationship in which the parts can exist independently of the whole.
Consider a department and its teachers. A department has teachers, but a teacher can exist independently and may be associated with another department later.
Example
class Teacher:
def __init__(self, name):
self.name = name
class Department:
def __init__(self, name, teachers):
self.name = name
self.teachers = teachers
def show_teachers(self):
print("Department:", self.name)
for teacher in self.teachers:
print(teacher.name)
t1 = Teacher("Mr Sharma")
t2 = Teacher("Ms Verma")
teachers = [t1, t2]
department = Department("Computer Science", teachers)
department.show_teachers()Output:
Department: Computer Science
Mr Sharma
Ms VermaExplanation
The teacher objects are created independently.
The department receives a list containing existing teacher objects.
The department refers to those objects but does not create them itself.
The teachers can continue to exist even if the department object is no longer used.
This illustrates aggregation.
Composition vs Association vs Aggregation
| Concept | Relationship | Object independence | Example |
|---|---|---|---|
| Association | Objects interact | Both can exist independently | Teacher and student |
| Aggregation | Weak whole-part relationship | Parts can exist independently | Department and teachers |
| Composition | Strong whole-part ownership | Parts are managed as components of the whole | Computer and its internally created processor |
Remember: Python does not have separate syntax for association, aggregation, and composition. These are object-oriented design concepts. The relationship depends on how objects are created, owned, shared, and managed.
13. Mini Project: Student Management System
Now let's combine several of the concepts we have learned into one small project.
This program demonstrates:
A constructor using
__init__().selfand instance variables.A class variable.
Instance methods.
A class method.
A static method.
Inheritance and method overriding.
super().
Complete Python program
class Student:
school = "ABC Public School"
def __init__(self, name, marks):
self.name = name
self.marks = marks
def display(self):
print("Name:", self.name)
print("Marks:", self.marks)
print("School:", self.school)
@classmethod
def change_school(cls, new_school):
cls.school = new_school
@staticmethod
def is_pass(marks):
return marks >= 33
class SportsStudent(Student):
def __init__(self, name, marks, sport):
super().__init__(name, marks)
self.sport = sport
def display(self):
super().display()
print("Sport:", self.sport)
student = Student("Aman", 78)
sports_student = SportsStudent("Priya", 85, "Basketball")
student.display()
print("Pass:", Student.is_pass(student.marks))
print()
sports_student.display()
print()
Student.change_school("Delhi Public School")
print("Updated school:", student.school)Expected output
Name: Aman
Marks: 78
School: ABC Public School
Pass: True
Name: Priya
Marks: 85
School: ABC Public School
Sport: Basketball
Updated school: Delhi Public SchoolWhat did we learn from the project?
The constructor initialises student objects.
selfidentifies the object whose details are being accessed.name,marks, andsportare instance variables.schoolis a class variable.display()is an instance method.change_school()is a class method.is_pass()is a static method.SportsStudentinherits fromStudent.The child class overrides
display().super()reuses the parent's initialisation and display behaviour.
This is how different OOP concepts work together in a real program.
Practice Questions
Test your understanding with the following questions.
Beginner Level
Q1. What is the purpose of __init__() in Python?
Q2. What does self represent in an instance method?
Q3. Identify the instance variable and class variable:
class Employee:
company = "Tech Solutions"
def __init__(self, name):
self.name = nameQ4. Which decorator is used to define a class method?
A. @static
B. @classmethod
C. @abstract
D. @instance
Intermediate Level
Q5. What is the difference between an instance method and a static method?
Q6. What is method overriding? Explain with an example.
Q7. Why do we use super() in inheritance?
Q8. Can an abstract class contain a normal method as well as an abstract method?
Q9. What is the difference between composition and aggregation?
Programming Challenges
Q10. Create a Book class with a constructor that stores the title, author, and price. Write a method to display these details.
Q11. Create an Employee class with a class variable company. Add a class method that changes the company name.
Q12. Create a Vehicle class with a method called start(). Create a Car child class that overrides start().
Q13. Create an abstract class called Shape with an abstract method area(). Implement it in Rectangle and Circle.
Q14. Create a Department class that stores a list of existing Teacher objects. Explain why this can be considered aggregation.
Conclusion
Python OOP is much more than creating classes and objects. Understanding constructors, self, instance and class variables, different types of methods, inheritance, method overriding, and super() helps you design programs that are easier to extend and maintain.
Abstract classes help define common interfaces, while association, aggregation, and composition help us model relationships between objects.
Once you understand these concepts, you can use them to develop practical projects such as student management systems, banking applications, inventory systems, and games.
Keep practising Python, experiment with the examples, and build your own projects. The best way to master OOP is to write code and understand how objects work together!
Further Reading
Explore our other Python Pebbles tutorials on:
Python Classes and Objects
Python Inheritance
Python Encapsulation
Python Polymorphism
Python Functions and Modules
Suggested next tutorial: Python OOP Project – Build a Student Management System Using Classes, Inheritance, and File Handling.
About Python Pebbles
Python Pebbles helps students and beginners learn Python programming through simple explanations, practical examples, exercises, and hands-on projects. Keep exploring, keep coding, and take one small step towards mastering Python every day.
No comments:
Post a Comment