Object-Oriented Programming In Python — PBA Institute Tutorial
Chapter 16 · Python Programming Series
12 min read Beginner

Mastering OOP in Python

Object-Oriented Programming organises code around objects — bundles of data and behaviour. Python is fully object-oriented: every value is an object, every function is callable. This chapter introduces the four pillars — encapsulation, inheritance, polymorphism, abstraction — and the syntax you'll use every day.

Overview

📦

Encapsulation

Group related data and behaviour into a single tidy unit.

🧬

Inheritance

Reuse and extend existing classes — DRY principle in action.

🎭

Polymorphism

One interface, many behaviours — uniform code over diverse types.

🪞

Abstraction

Expose what an object does; hide how it does it.

🔧

Dunder Methods

Make your objects work with print, equality, len, and arithmetic operators.

Syntax

  • Define a class with class Name:.
  • Add an initializer with def __init__(self, ...):.
  • Methods take self as the first parameter.
  • Create objects by calling the class: obj = Name(args).
OOP in Python — Syntax
class Car:
    def __init__(self, brand, speed=0):
        self.brand = brand
        self.speed = speed

    def accelerate(self, delta):
        self.speed += delta

    def __str__(self):
        return f"{self.brand} @ {self.speed} km/h"

c = Car("Tesla")
c.accelerate(50)
print(c)

Detailed Explanation

  • Classes & Objects: A class describes a category of things; an object is one instance of that class. Car is a class, my_tesla is an object.
  • Encapsulation: Bundling data and behaviour together. Internal details are hidden behind methods; users interact through a clean API.
  • Inheritance: A subclass reuses and extends a parent class. ElectricCar(Car) automatically has every method of Car plus its own.
  • Polymorphism: Many forms of one interface. Different classes can implement __str__ or area() their own way, but callers use them uniformly.
  • Abstraction: Hide complexity. Users call car.start() without caring how ignition works internally. abc.ABC + @abstractmethod enforce abstraction.
  • Special methods: __init__, __str__, __repr__, __eq__, __len__ let your objects integrate with Python's built-in syntax (print, equality, len).

Code Examples

Example 1 — Define a Class
class Dog:
    def __init__(self, name):
        self.name = name
    def bark(self):
        return f"{self.name} says Woof!"

d = Dog("Bruno")
print(d.bark())
Output Bruno says Woof!
Example 2 — Multiple Objects
class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

b1 = Book("Python", 499)
b2 = Book("Django", 699)
print(b1.title, b1.price)
print(b2.title, b2.price)
Output Python 499
Django 699
Example 3 — Inheritance
class Animal:
    def speak(self):
        return "generic sound"

class Cat(Animal):
    def speak(self):
        return "Meow"

print(Animal().speak())
print(Cat().speak())
Output generic sound
Meow
Example 4 — Polymorphism
class Square:
    def __init__(self, s): self.s = s
    def area(self): return self.s * self.s

class Circle:
    def __init__(self, r): self.r = r
    def area(self): return 3.14 * self.r ** 2

for shape in [Square(4), Circle(5)]:
    print(shape.area())
Output 16
78.5
Example 5 — Encapsulation (Private)
class Account:
    def __init__(self, balance):
        self.__balance = balance
    def deposit(self, amt):
        self.__balance += amt
    def get_balance(self):
        return self.__balance

a = Account(1000)
a.deposit(500)
print(a.get_balance())
Output 1500
Example 6 — Dunder Methods
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __add__(self, o):
        return Vector(self.x + o.x, self.y + o.y)
    def __str__(self):
        return f"({self.x},{self.y})"

print(Vector(1,2) + Vector(3,4))
Output (4,6)

Real-World Use Cases

User Models

Each user becomes a class with fields and behaviours.

E-commerce Products

Product, Cart, Order — natural class hierarchy.

Game Engines

Sprites, scenes, and game-states modelled as objects.

Hardware Drivers

Each device is an object exposing read/write methods.

Banking Apps

Account, Transaction, Customer — encapsulated business rules.

Notifications

Email, SMS, Push as subclasses of a common Notifier interface.

Notes & Pro Tips

  • Use self for the current instance; cls for the class itself in classmethods.
  • Naming: classes CamelCase, methods/attrs snake_case.
  • Single underscore prefix _x = soft private; double underscore __x = name-mangled.
  • Prefer composition (has-a) over deep inheritance (is-a) for flexibility.
  • Use @dataclass for plain data classes — auto-generates __init__, __repr__, __eq__.
  • Override __repr__ for debugging; __str__ for user display.

Common Mistakes

  • Forgetting self: first parameter of every instance method must be self.
  • Confusing class and instance attributes: setting on the class affects all instances.
  • Mutable class-level defaults: share state across instances — surprising bugs.
  • Overusing inheritance: deep hierarchies are brittle; favour composition.
  • Accessing private: obj.__name from outside is name-mangled — by design.
  • Missing __init__: objects can't store instance state without it.

Practice Problems

  • Problem 1: Create a class Rectangle with attributes length and width and methods area() and perimeter().
  • Problem 2: Build a class BankAccount with deposit, withdraw, and balance methods.
  • Problem 3: Create classes Shape, Square, and Circle demonstrating polymorphism.
  • Problem 4: Build a class hierarchy: EmployeeManagerDirector.
  • Problem 5: Make a Vector class supporting addition, subtraction, and string representation.
  • Problem 6: Create a Student class with private marks and methods to compute grade.

Interview Questions

  • Q1. What are the four pillars of OOP?
  • Q2. What is the difference between a class and an object?
  • Q3. Explain inheritance with an example.
  • Q4. What is polymorphism and how does Python implement it?
  • Q5. What is the role of self in Python classes?
  • Q6. Difference between __str__ and __repr__?

Frequently Asked Questions

  • Q1: What is OOP?
    Object-Oriented Programming is a paradigm that organises code as objects — bundles of data and behaviour modelled on real entities.
  • Q2: What are the four pillars of OOP?
    Encapsulation, Inheritance, Polymorphism, Abstraction.
  • Q3: Do I need OOP for every program?
    No. Small scripts may be purely functional. OOP shines when you have multiple related entities or want long-term maintainability.
  • Q4: What is composition vs inheritance?
    Inheritance creates 'is-a' relationships; composition creates 'has-a' relationships by holding other objects as attributes. Composition is more flexible.
  • Q5: What is a dunder method?
    Methods like __init__, __str__, __add__ that Python calls implicitly. They let your objects integrate with operators and built-ins.
  • Q6: Why use private attributes?
    To protect internal state from accidental external modification and signal which API is stable.

Summary

Object-Oriented Programming structures code around objects — modelling real-world entities and their interactions. With encapsulation, inheritance, polymorphism, and abstraction you can build large, reusable, maintainable systems. Python's clean OOP syntax — classes, self, dunder methods, dataclasses — makes the paradigm both accessible and powerful.

Continue Learning

Previous

Go to Previous Chapter

Next

Go to Next Chapter