Skip to main content
  1. Blog/

Essential Object-Oriented Programming Summary in Python

··6 mins
Object-Oriented Programming (OOP) is one of the most widely used paradigms in software development. In this guide, we will explore everything from the basic building blocks to advanced and Pythonic concepts to master OOP in Python.

1. Fundamentals: From Concept to Code #

Before diving into the pillars of OOP, it is vital to understand the pieces that make up this paradigm and how they differ from traditional structured programming.

Function vs. Method #

A function is an independent, reusable block of code that performs a specific task. A method is essentially a function, but it “lives” inside a class and operates on the data (state) of the objects created from that class.

# This is a function (independent)
def greet(name):
    return f"Hello, {name}!"

class Person:
    def __init__(self, name):
        self.name = name

    # This is a method (belongs to the class)
    def greet_method(self):
        return f"Hi, I am {self.name}"

Class, Object, and Constructor #

  • Class: It is the blueprint or template. It defines what attributes (data) and methods (behaviors) the objects will have.
  • Object (Instance): It is the materialization of that class. If the class is the blueprint of a house, the object is the built house.
  • Constructor (__init__): It is a special method that Python automatically executes every time you create a new object. It is used to initialize the object’s state.
class Car:
    # The constructor initializes the state
    def __init__(self, make, model):
        self.make = make   # Attribute
        self.model = model # Attribute

# Instantiation: creating objects from the blueprint
my_car = Car("Toyota", "Corolla")
print(my_car.make) # Output: Toyota

Design Concepts:

  • Entity: The abstract concept in your business logic (e.g., a Car).
  • Property: The conceptual characteristic of that entity (e.g., the color).
  • Attribute: The actual variable in the code that stores that property (self.color).

2. The 4 Pillars of OOP #

Object-Oriented Programming is built upon four fundamental concepts that ensure modular, secure, and reusable code.

A. Encapsulation #

Encapsulation involves protecting the internal state of an object, preventing it from being modified directly from the outside in an uncontrolled way.

In Python, there are no strict access modifiers like private or public in Java, but the convention of underscores is used (_ for protected, __ for private). To interact with this data, we use Getters and Setters.

The modern and Pythonic way to implement them is by using the @property decorator:

class Product:
    def __init__(self, initial_price):
        self.__price = initial_price # "Private" attribute

    @property
    def price(self):
        """Getter: Returns the value."""
        return self.__price

    @price.setter
    def price(self, new_price):
        """Setter: Validates before modifying."""
        if new_price < 0:
            raise ValueError("Price cannot be negative")
        self.__price = new_price

p = Product(100)
p.price = 150  # Automatically calls the setter
print(p.price) # Automatically calls the getter

B. Inheritance and Overriding #

Inheritance allows creating new classes (child classes) based on existing classes (parent classes), inheriting their attributes and methods.

Overriding occurs when the child class redefines a method of the parent class to give it a specific behavior, often using super() to leverage the original logic.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def calculate_bonus(self):
        return self.salary * 0.10

class Manager(Employee):
    # Method overriding
    def calculate_bonus(self):
        # We use super() to get the base bonus and add an extra amount
        base_bonus = super().calculate_bonus()
        return base_bonus + 500

m = Manager("Anna", 1000)
print(m.calculate_bonus()) # Output: 600.0 (100 base + 500 extra)

Multiple Inheritance #

Python supports multiple inheritance, meaning a class can inherit from several classes at once. Python handles potential method conflicts using the MRO (Method Resolution Order).

class Flyer:
    def fly(self): return "Flying high"

class Swimmer:
    def swim(self): return "Swimming deep"

class Duck(Flyer, Swimmer): # Inherits from both
    pass

donald = Duck()
print(donald.fly(), "and", donald.swim())

C. Abstraction and Interfaces #

Abstraction consists of exposing only the essential details and hiding the internal complexity. It is implemented using Abstract Classes. An abstract class cannot be instantiated and serves as a base blueprint.

An Interface is a strict form of abstraction. It acts as a “contract”: it defines what an object must be able to do (its methods), but it does not include any logic on how it should do it. It guarantees that any class implementing that interface will have those methods available.

In Python, this is achieved with the abc (Abstract Base Classes) module. If an abstract class only contains abstract methods (without any implementation), it functions as an interface.

from abc import ABC, abstractmethod

# This acts as an Interface (only defines the "what")
class PaymentProcessor(ABC):
    @abstractmethod
    def process(self, amount):
        pass

class CreditCardPayment(PaymentProcessor):
    # Forced to implement the "how"
    def process(self, amount):
        print(f"Processing ${amount} with Credit Card")

class PayPalPayment(PaymentProcessor):
    def process(self, amount):
        print(f"Processing ${amount} through PayPal")

D. Polymorphism #

Polymorphism (“many forms”) allows different objects to respond to the same method in their own way. It combines perfectly with abstraction.

Using the previous example, we can create a function that processes payments regardless of the exact object type, relying on the interface’s contract:

def process_payment(processor: PaymentProcessor, amount: float):
    # Polymorphism in action: 'processor' can be CreditCard or PayPal
    processor.process(amount)

process_payment(CreditCardPayment(), 1500)
process_payment(PayPalPayment(), 500)

3. Advanced Concepts and Tools #

To master OOP in Python, you must know certain tools the language offers to better structure classes and ensure typing.

Decorators #

A decorator is a function that wraps another function (or method) to extend or modify its behavior without altering its source code. In OOP, we use them constantly (as we saw with @property), but we can also create our own.

# Creating a custom decorator
def audit_logger(method):
    def wrapper(*args, **kwargs):
        print(f"-> LOG: Executing method '{method.__name__}'")
        return method(*args, **kwargs)
    return wrapper

class System:
    @audit_logger
    def start(self):
        print("The system has started successfully.")

s = System()
s.start()
# Expected output:
# -> LOG: Executing method 'start'
# The system has started successfully.

Instance, Class, and Static Methods #

Python offers different ways to bind methods within a class:

  1. Instance Method (default): Takes self as the first parameter. Reads or modifies the object’s state.
  2. Class Method (@classmethod): Takes cls (the class itself) as the first parameter. Used to modify the general state of the class or as alternative constructors (Factory methods).
  3. Static Method (@staticmethod): Takes neither self nor cls. It is basically a normal function placed inside the class for logical organization, since it does not need to access class or object properties.
class Date:
    def __init__(self, day, month, year):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_str):
        """Alternative constructor. Takes 'cls' instead of 'self'."""
        day, month, year = map(int, date_str.split('-'))
        return cls(day, month, year)

    @staticmethod
    def is_leap_year(year):
        """Pure logic. Doesn't need to access 'self' or 'cls'."""
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

# Using the classmethod (Factory)
today = Date.from_string("22-07-2025")

# Using the staticmethod
print(Date.is_leap_year(2024)) # True

Generics (Generic Typing) #

With Python’s evolution towards optional static typing, we can use Generics to create classes that can work with any data type while maintaining type safety in static analysis (ideal for IDEs or tools like mypy).

from typing import TypeVar, Generic

# Declare a type variable T
T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content: T):
        self.content = content

    def get_content(self) -> T:
        return self.content

# Upon instantiation, we specify the expected type
box_ints = Box[int](123)
box_strings = Box[str]("A message")
Gerardo Catalas
Author
Gerardo Catalas
Focused on backend development, with hands-on experience in C# (.NET) and SQL Server. Currently expanding my skills through continuous learning and personal projects.