Object-Oriented Programming (OOP) is a programming paradigm based on the concept of “objects” — containers that bundle data and behavior together. Python, while supporting multiple paradigms, is an object-oriented language at its core. Understanding OOP is essential for writing structured, maintainable, and scalable code.
In this chapter, we will explore OOP fundamentals in Python: classes, objects, attributes, methods, constructors, inheritance, polymorphism, encapsulation, abstraction, and real-world design patterns. You’ll also learn best practices, common pitfalls, and how to structure large applications using OOP.
Table of Contents
What is Object-Oriented Programming?
OOP organizes code around objects and classes instead of functions and logic.
Key Concepts:
- Class: Blueprint for creating objects
- Object: Instance of a class
- Attribute: Data associated with an object
- Method: Function associated with an object
Real-world Analogy:
A class is like a car blueprint, and an object is the actual car built from that blueprint.
Creating a Class
Syntax:
class ClassName:
# class body
Example:
class Dog:
def bark(self):
print("Woof!")
Creating Objects (Instances)
my_dog = Dog()
my_dog.bark() # Output: Woof!
Each object has its own identity and can access class methods.
The __init__() Method (Constructor)
Special method that runs automatically when an object is created.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
Creating Objects:
d = Dog("Buddy", 3)
print(d.name, d.age)
Instance and Class Attributes
Instance Attributes:
Defined using self inside the __init__() method.
Class Attributes:
Defined outside all methods, shared among all instances.
class Dog:
species = "Canine"
Instance Methods
Most common type, defined with self as the first parameter.
class Dog:
def speak(self):
print(f"My name is {self.name}")
Class Methods and Static Methods
Class Methods:
Defined with @classmethod, receive class (cls) instead of instance.
@classmethod
def from_string(cls, data):
name, age = data.split("-")
return cls(name, int(age))
Static Methods:
Defined with @staticmethod, behave like regular functions but belong to the class namespace.
@staticmethod
def greet():
print("Welcome!")
Encapsulation
Protecting internal object data by making attributes private or protected.
class Account:
def __init__(self, balance):
self.__balance = balance # private
def get_balance(self):
return self.__balance
Inheritance
Allows a class to inherit attributes and methods from another class.
Syntax:
class ChildClass(ParentClass):
# additional members
Example:
class Animal:
def speak(self):
print("Animal sound")
class Cat(Animal):
def speak(self):
print("Meow")
super() Function:
Calls parent class method.
class Dog(Animal):
def __init__(self):
super().__init__()
Polymorphism
Ability to use a common interface for different data types.
class Bird:
def speak(self):
print("Chirp")
class Human:
def speak(self):
print("Hello")
for entity in [Bird(), Human()]:
entity.speak() # Different outputs
Abstraction
Hiding implementation details and showing only the interface.
Abstract Base Classes:
Use abc module.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
Special Methods (Dunder Methods)
Double underscore (dunder) methods customize class behavior.
| Method | Purpose |
|---|---|
__str__ | String representation |
__repr__ | Official representation |
__len__ | Length of object |
__eq__ | Equality comparison |
__add__ | Overload + operator |
Example:
class Book:
def __init__(self, title):
self.title = title
def __str__(self):
return self.title
Composition vs Inheritance
Composition:
Class contains other class objects.
class Engine:
pass
class Car:
def __init__(self):
self.engine = Engine()
Use composition when a “has-a” relationship makes more sense than “is-a”.
Real-World Example: Employee Management System
class Employee:
def __init__(self, name, id):
self.name = name
self.id = id
def show(self):
print(f"Name: {self.name}, ID: {self.id}")
class Manager(Employee):
def __init__(self, name, id, department):
super().__init__(name, id)
self.department = department
def show(self):
super().show()
print(f"Department: {self.department}")
Best Practices
- Use
selfconsistently - Favor composition over deep inheritance chains
- Encapsulate data properly
- Use class methods for alternative constructors
- Use
__str__()for meaningful object representation - Write unit tests for class methods
Common Mistakes
- Forgetting
selfin instance methods - Overriding methods without calling
super() - Using mutable default arguments in
__init__ - Confusing class and instance variables
Debugging Tips:
- Use
type()anddir()to inspect objects - Add
print()statements to trace method calls
Exercises
- Create a class
Rectanglewith width, height and area methods. - Implement inheritance with
VehicleandCarclasses. - Use
@classmethodto instantiate an object from a string. - Implement a
BankAccountclass with encapsulated balance. - Create a polymorphic function
speak()used byDog,Cat, andBirdclasses. - Use the
abcmodule to define and implement an abstract base class.
Summary
Object-Oriented Programming is a cornerstone of software development. With classes, objects, encapsulation, inheritance, polymorphism, and abstraction, Python allows developers to model real-world entities in code. Mastering OOP leads to cleaner, more maintainable, and reusable code — a vital skill for any serious Python developer.
✅ Next Chapter: Modules and Packages – Learn how to organize your Python codebase using modular and scalable structures.
