Progress0%

8 of 20 topics completed

Python Object-Oriented Programming (OOP)

Object-Oriented Programming (OOP) is a powerful paradigm that helps you create organized, reusable, and modular code. In this tutorial, you'll learn how to use classes and objects in Python to model real-world concepts and build more maintainable programs.

💡 Real-World Relevance

OOP is essential in large-scale software development. Almost all modern frameworks and libraries (like Django, Flask, PyQt, etc.) are built using OOP principles. Understanding OOP will help you build more complex applications and better understand existing codebases.

What is Object-Oriented Programming?

OOP is a programming paradigm based on the concept of "objects," which can contain:

  • Data (attributes or properties)
  • Functions (methods) that operate on that data

Think of objects as digital representations of real-world entities. For example, a "Student" object might have:

OOP Concept Diagram

Attributes (Data)

  • Name
  • Age
  • Grade
  • Courses

Methods (Functions)

  • enroll_in_course()
  • calculate_gpa()
  • print_schedule()
  • graduate()

Classes and Objects in Python

In Python, a class is a blueprint for creating objects. An objectis an instance of a class. Let's look at a simple example:

Python
1# Define a class
2class Dog:
3 # Class attribute (shared by all instances)
4 species = "Canis familiaris"
5
6 # Initialize method (constructor)
7 def __init__(self, name, age):
8 # Instance attributes (unique to each instance)
9 self.name = name
10 self.age = age
11
12 # Instance method
13 def bark(self):
14 return f"{self.name} says Woof!"
15
16 # Another instance method
17 def get_info(self):
18 return f"{self.name} is {self.age} years old"
19
20# Create objects (instances of the Dog class)
21buddy = Dog("Buddy", 5)
22max = Dog("Max", 3)
23
24# Access attributes
25print(buddy.name) # Output: Buddy
26print(max.age) # Output: 3
27print(buddy.species) # Output: Canis familiaris
28
29# Call methods
30print(buddy.bark()) # Output: Buddy says Woof!
31print(max.get_info()) # Output: Max is 3 years old

Key Components

  • __init__ method: Constructor that initializes new objects
  • self parameter: Reference to the current instance
  • Instance attributes: Unique to each object (e.g., name, age)
  • Class attributes: Shared by all instances (e.g., species)
  • Methods: Functions defined in the class

The 'self' Parameter

The self parameter refers to the instance of the class. It:

  • Must be the first parameter of any instance method
  • Allows access to the instance's attributes and methods
  • Is automatically passed when you call a method on an object
  • Works like "this" in other programming languages

Inheritance: Building on Existing Classes

Inheritance allows you to create a new class that is a modified version of an existing class. The new class (subclass) inherits attributes and methods from the existing class (superclass).

Python
1# Parent class
2class Animal:
3 def __init__(self, name, species):
4 self.name = name
5 self.species = species
6
7 def make_sound(self):
8 return "Some generic animal sound"
9
10 def get_info(self):
11 return f"{self.name} is a {self.species}"
12
13# Child class (inherits from Animal)
14class Cat(Animal):
15 def __init__(self, name, breed, toy):
16 # Call the parent class's __init__ method
17 super().__init__(name, species="Cat")
18 self.breed = breed
19 self.toy = toy
20
21 # Override the parent's method
22 def make_sound(self):
23 return "Meow!"
24
25 # Add a new method
26 def play(self):
27 return f"{self.name} plays with {self.toy}"
28
29# Create an instance of the Cat class
30whiskers = Cat("Whiskers", "Siamese", "String")
31
32# Use inherited methods and attributes
33print(whiskers.name) # Output: Whiskers
34print(whiskers.species) # Output: Cat
35print(whiskers.get_info()) # Output: Whiskers is a Cat
36
37# Use overridden and new methods
38print(whiskers.make_sound()) # Output: Meow!
39print(whiskers.play()) # Output: Whiskers plays with String
Inheritance Diagram

Key Inheritance Concepts:

  • Parent class/Superclass: The class being inherited from
  • Child class/Subclass: The class that inherits from the parent
  • super(): Used to call methods from the parent class
  • Method overriding: Redefining a method in a subclass
  • IS-A relationship: A Cat is an Animal

Encapsulation: Controlling Access to Data

Encapsulation is the concept of bundling data and methods that work on that data within a single unit (the class) and restricting access to some of the object's components.

Python uses conventions rather than strict access control:

Python
1class BankAccount:
2 def __init__(self, owner, balance=0):
3 self.owner = owner # Public attribute
4 self._balance = balance # Protected attribute (convention)
5 self.__account_num = "12345" # Private attribute
6
7 # Public method
8 def deposit(self, amount):
9 if amount > 0:
10 self._balance += amount
11 return True
12 return False
13
14 # Public method
15 def withdraw(self, amount):
16 if 0 < amount <= self._balance:
17 self._balance -= amount
18 return True
19 return False
20
21 # Public method to access protected attribute
22 def get_balance(self):
23 return self._balance
24
25 # Private method
26 def __generate_statement(self):
27 return f"Statement for {'{'} self.owner {'}'}: Balance: { self._balance {'}'}"
28
29 # Public method that uses private method
30 def print_statement(self):
31 statement = self.__generate_statement()
32 print(statement)
33
34# Create an account
35account = BankAccount("Alice", 1000)
36
37# Access public members
38print(account.owner) # Output: Alice
39account.deposit(500)
40print(account.get_balance()) # Output: 1500
41
42# Access protected member (possible, but not recommended)
43print(account._balance) # Output: 1500
44
45# Try to access private member (name mangling occurs)
46# print(account.__account_num) # AttributeError
47print(account._BankAccount__account_num) # Output: 12345 (name mangling)
Access LevelNaming ConventionAccessibility
PublicnameAccessible from anywhere
Protected_nameConvention indicating "internal use" (still accessible)
Private__nameName mangling occurs, harder to access from outside

⚠️ Python's Approach

Python follows the principle of "we're all consenting adults here" - encapsulation is more of a guideline than a strict rule. Private attributes can still be accessed using name mangling (_ClassName__attribute), but it's considered bad practice to do so.

Polymorphism: One Interface, Multiple Forms

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It means you can use the same interface (method or function) for different data types.

Python
1# Different classes with the same method
2class Dog:
3 def speak(self):
4 return "Woof!"
5
6class Cat:
7 def speak(self):
8 return "Meow!"
9
10class Duck:
11 def speak(self):
12 return "Quack!"
13
14# Function that can work with any of these objects
15def animal_sound(animal):
16 return animal.speak()
17
18# Create instances
19dog = Dog()
20cat = Cat()
21duck = Duck()
22
23# Call the function with different objects
24print(animal_sound(dog)) # Output: Woof!
25print(animal_sound(cat)) # Output: Meow!
26print(animal_sound(duck)) # Output: Quack!
27
28# Using polymorphism with a list
29animals = [Dog(), Cat(), Duck()]
30for animal in animals:
31 print(animal.speak())

Python's "duck typing" is a form of polymorphism: "If it walks like a duck and quacks like a duck, then it probably is a duck." This means Python cares about behavior (methods and properties), not the actual type of an object.

Special (Magic/Dunder) Methods

Python has special methods surrounded by double underscores (Dunder = Double UNDERscore) that allow you to define how objects of your class behave with built-in functions and operators.

Python
1class Point:
2 def __init__(self, x, y):
3 self.x = x
4 self.y = y
5
6 # String representation for developers (debugging)
7 def __repr__(self):
8 return f"Point({self.x}, {self.y})"
9
10 # String representation for users
11 def __str__(self):
12 return f"Point at coordinates ({self.x}, {self.y})"
13
14 # Addition operator (+)
15 def __add__(self, other):
16 return Point(self.x + other.x, self.y + other.y)
17
18 # Equality operator (==)
19 def __eq__(self, other):
20 return self.x == other.x and self.y == other.y
21
22 # Less than operator (<)
23 def __lt__(self, other):
24 return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
25
26 # Length function (len())
27 def __len__(self):
28 # Distance from origin, rounded to nearest integer
29 return int((self.x**2 + self.y**2)**0.5)
30
31# Create points
32p1 = Point(3, 4)
33p2 = Point(1, 2)
34
35# Use our special methods
36print(p1) # Output: Point at coordinates (3, 4)
37print(repr(p1)) # Output: Point(3, 4)
38p3 = p1 + p2 # Using __add__
39print(p3) # Output: Point at coordinates (4, 6)
40print(p1 == Point(3, 4)) # Output: True (using __eq__)
41print(p1 < p2) # Output: False (using __lt__)
42print(len(p1)) # Output: 5 (using __len__)
Special MethodPurposeExample Usage
__init__(self, ...)Constructorx = MyClass()
__str__(self)String representationprint(obj), str(obj)
__repr__(self)Developer representationrepr(obj)
__len__(self)Lengthlen(obj)
__add__(self, other)Additionobj1 + obj2
__eq__(self, other)Equalityobj1 == obj2

Practical Example: Building a Student Management System

Let's apply OOP principles to build a simple student management system:

Python
1class Person:
2 """Base class for all persons in the system."""
3 def __init__(self, name, age):
4 self.name = name
5 self.age = age
6
7 def get_info(self):
8 return f"{self.name}, {self.age} years old"
9
10
11class Student(Person):
12 """A student in the system."""
13 def __init__(self, name, age, student_id):
14 super().__init__(name, age)
15 self.student_id = student_id
16 self.courses = []
17 self.grades = {}
18
19 def enroll(self, course):
20 if course not in self.courses:
21 self.courses.append(course)
22 self.grades[course] = None
23 return True
24 return False
25
26 def assign_grade(self, course, grade):
27 if course in self.courses:
28 self.grades[course] = grade
29 return True
30 return False
31
32 def get_gpa(self):
33 grades = [g for g in self.grades.values() if g is not None]
34 if not grades:
35 return 0
36 return sum(grades) / len(grades)
37
38 def get_info(self):
39 base_info = super().get_info()
40 return f"{base_info}, ID: {self.student_id}, GPA: {self.get_gpa():.2f}"
41
42
43class Teacher(Person):
44 """A teacher in the system."""
45 def __init__(self, name, age, employee_id, subject):
46 super().__init__(name, age)
47 self.employee_id = employee_id
48 self.subject = subject
49 self.students = []
50
51 def assign_student(self, student):
52 if student not in self.students:
53 self.students.append(student)
54 student.enroll(self.subject)
55 return True
56 return False
57
58 def grade_student(self, student, grade):
59 if student in self.students:
60 return student.assign_grade(self.subject, grade)
61 return False
62
63 def get_class_average(self):
64 grades = [s.grades.get(self.subject, 0) for s in self.students
65 if s.grades.get(self.subject) is not None]
66 if not grades:
67 return 0
68 return sum(grades) / len(grades)
69
70 def get_info(self):
71 base_info = super().get_info()
72 return f"{base_info}, ID: {self.employee_id}, Subject: {self.subject}"
73
74
75class School:
76 """A school containing students and teachers."""
77 def __init__(self, name):
78 self.name = name
79 self.students = []
80 self.teachers = []
81
82 def add_student(self, student):
83 if student not in self.students:
84 self.students.append(student)
85 return True
86 return False
87
88 def add_teacher(self, teacher):
89 if teacher not in self.teachers:
90 self.teachers.append(teacher)
91 return True
92 return False
93
94 def get_student_by_id(self, student_id):
95 for student in self.students:
96 if student.student_id == student_id:
97 return student
98 return None
99
100 def get_teacher_by_subject(self, subject):
101 return [t for t in self.teachers if t.subject == subject]
102
103 def __str__(self):
104 return f"{self.name} School with {len(self.students)} students and {len(self.teachers)} teachers"
105
106
107# Example usage
108def main():
109 # Create a school
110 school = School("Springfield Elementary")
111
112 # Create teachers
113 math_teacher = Teacher("Mrs. Adams", 45, "T001", "Mathematics")
114 science_teacher = Teacher("Mr. Smith", 38, "T002", "Science")
115
116 # Add teachers to school
117 school.add_teacher(math_teacher)
118 school.add_teacher(science_teacher)
119
120 # Create students
121 alice = Student("Alice Johnson", 12, "S001")
122 bob = Student("Bob Williams", 11, "S002")
123 charlie = Student("Charlie Brown", 12, "S003")
124
125 # Add students to school
126 school.add_student(alice)
127 school.add_student(bob)
128 school.add_student(charlie)
129
130 # Assign students to teachers
131 math_teacher.assign_student(alice)
132 math_teacher.assign_student(bob)
133 math_teacher.assign_student(charlie)
134
135 science_teacher.assign_student(alice)
136 science_teacher.assign_student(charlie)
137
138 # Assign grades
139 math_teacher.grade_student(alice, 95)
140 math_teacher.grade_student(bob, 88)
141 math_teacher.grade_student(charlie, 75)
142
143 science_teacher.grade_student(alice, 92)
144 science_teacher.grade_student(charlie, 85)
145
146 # Print information
147 print(school)
148 print(f"Math class average: {math_teacher.get_class_average():.2f}")
149 print(f"Science class average: {science_teacher.get_class_average():.2f}")
150
151 print("
152Student Information:")
153 for student in school.students:
154 print(student.get_info())
155
156 print("
157Teacher Information:")
158 for teacher in school.teachers:
159 print(teacher.get_info())
160
161# Run the program
162main()

Output of the above program:

Python
1Springfield Elementary School with 3 students and 2 teachers
2Math class average: 86.00
3Science class average: 88.50
4
5Student Information:
6Alice Johnson, 12 years old, ID: S001, GPA: 93.50
7Bob Williams, 11 years old, ID: S002, GPA: 88.00
8Charlie Brown, 12 years old, ID: S003, GPA: 80.00
9
10Teacher Information:
11Mrs. Adams, 45 years old, ID: T001, Subject: Mathematics
12Mr. Smith, 38 years old, ID: T002, Subject: Science

🎯 Try it yourself!

Extend the Student Management System with these features:

  • Add a Course class with properties like name, code, and credits
  • Implement attendance tracking for students
  • Add a method to generate a report card for each student
  • Create an Administrator class that can manage both students and teachers

Best Practices for OOP in Python

  1. Follow naming conventions
    • Class names should use CamelCase: MyClass
    • Method and attribute names should use snake_case: my_method
    • Constants should be UPPERCASE: MAX_STUDENTS
  2. Use docstrings to document your classes and methods
  3. Keep classes focused on a single responsibility (Single Responsibility Principle)
  4. Use inheritance wisely - prefer composition over inheritance when appropriate
  5. Don't expose internal details - use properties and methods to control access
  6. Implement special methods when they make sense for your class

Summary

In this tutorial, you've learned:

  • The core concepts of Object-Oriented Programming
  • How to create classes and objects in Python
  • Inheritance and how to create class hierarchies
  • Encapsulation and data hiding techniques
  • Polymorphism and duck typing
  • Special methods to integrate with Python's built-in functions
  • How to apply OOP principles to a real-world example

Object-Oriented Programming is a powerful paradigm that helps you organize and structure your code in a way that models real-world relationships. As you continue your Python journey, you'll find these principles invaluable for creating maintainable, reusable, and scalable code.

Related Tutorials

Master creating and using functions in Python.

Learn more

Learn how to handle errors and exceptions in Python.

Learn more

Organize your code into reusable modules and packages.

Learn more