Skip to content

Python Programming Paradigms

This article introduces the main programming paradigms supported by Python: procedural, functional, and object-oriented, along with the appropriate use cases for each.

What Is a Programming Paradigm

A programming paradigm is a framework for thinking about and organizing code when solving problems. Python is a multi-paradigm language — the same problem can be solved using different paradigms:

  • Procedural: Uses functions to sequence the solution steps.
  • Functional: Treats computation as the evaluation of mathematical functions, emphasizing the absence of side effects.
  • Object-Oriented: Encapsulates data and behavior within objects, solving problems through object collaboration.

Procedural Programming

Procedural programming centers on “procedures (steps)” — do this first, then that. It suits linear data-processing pipelines.

# Example: count occurrences of each word in a file
import re
from pathlib import Path

def read_file(path: str) -> str:
    return Path(path).read_text(encoding="utf-8")

def extract_words(text: str) -> list[str]:
    return re.findall(r"\b\w+\b", text.lower())

def count_words(words: list[str]) -> dict[str, int]:
    result: dict[str, int] = {}
    for word in words:
        result[word] = result.get(word, 0) + 1
    return result

def top_n(counts: dict[str, int], n: int = 10) -> list[tuple[str, int]]:
    return sorted(counts.items(), key=lambda x: x[1], reverse=True)[:n]

# Main flow: steps are clear, but extensibility is poor
text = read_file("article.txt")
words = extract_words(text)
counts = count_words(words)
print(top_n(counts))

Pros: Logic is intuitive; great for one-off scripts.
Cons: As requirements grow, the steps become tightly coupled, making it hard to extend and maintain.

Functional Programming

The core idea of functional programming: functions are first-class citizens; shared state and side effects are avoided; logic is described through data transformations (map/filter/reduce).

from functools import reduce
import re

# Rewrite the word-count example in functional style
text = "Hello world hello Python world"

# Each step is a pure function (same input → same output, no side effects)
words = list(map(str.lower, re.findall(r"\b\w+\b", text)))

counts = reduce(
    lambda acc, w: {**acc, w: acc.get(w, 0) + 1},
    words,
    {}
)

top5 = sorted(counts.items(), key=lambda x: x[1], reverse=True)[:5]
print(top5)

Functional features as expressed in Python:

from functools import partial, reduce

# Higher-order functions: a function that takes or returns a function
def apply_twice(f, x):
    return f(f(x))

print(apply_twice(lambda x: x * 2, 3))   # 12

# partial: fix some arguments to create a new function
from functools import partial

def power(base: int, exp: int) -> int:
    return base ** exp

square = partial(power, exp=2)
cube = partial(power, exp=3)

print(square(5))   # 25
print(cube(3))     # 27

# Immutable data + comprehensions: avoid mutating the original data
original = [1, 2, 3, 4, 5]
doubled = [x * 2 for x in original]   # produces a new list; original is unchanged

Object-Oriented Programming

Object-oriented programming centers on “objects”, bundling related data and behavior together. It suits complex systems and projects that need long-term maintenance.

from dataclasses import dataclass, field
from collections import Counter
import re

@dataclass
class WordCounter:
    """Count word frequencies in a text."""
    text: str
    _words: list[str] = field(default_factory=list, init=False, repr=False)

    def __post_init__(self):
        self._words = re.findall(r"\b\w+\b", self.text.lower())

    @property
    def counts(self) -> dict[str, int]:
        return dict(Counter(self._words))

    def top_n(self, n: int = 10) -> list[tuple[str, int]]:
        return Counter(self._words).most_common(n)

    def __len__(self) -> int:
        return len(self._words)

# Usage
wc = WordCounter("Hello world hello Python world")
print(wc.counts)       # {'hello': 2, 'world': 2, 'python': 1}
print(wc.top_n(3))     # [('hello', 2), ('world', 2), ('python', 1)]
print(len(wc))         # 5

The three pillars of OOP as applied in Python:

# Encapsulation: control access via properties
class BankAccount:
    def __init__(self, balance: float):
        self.__balance = balance   # private; cannot be accessed directly from outside

    @property
    def balance(self) -> float:
        return self.__balance

    def deposit(self, amount: float) -> None:
        if amount > 0:
            self.__balance += amount

# Inheritance: reuse parent-class logic
class SavingsAccount(BankAccount):
    def __init__(self, balance: float, interest_rate: float):
        super().__init__(balance)
        self.interest_rate = interest_rate

    def apply_interest(self) -> None:
        self.deposit(self.balance * self.interest_rate)

# Polymorphism: different object types respond to the same interface
class Shape:
    def area(self) -> float:
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, r: float):
        self.r = r
    def area(self) -> float:
        import math
        return math.pi * self.r ** 2

class Rectangle(Shape):
    def __init__(self, w: float, h: float):
        self.w = w
        self.h = h
    def area(self) -> float:
        return self.w * self.h

shapes: list[Shape] = [Circle(5), Rectangle(4, 6)]
total_area = sum(s.area() for s in shapes)   # each calls its own area()

Abstract Classes and Interfaces

Python implements abstract classes via the abc module, which forces subclasses to implement specific methods:

from abc import ABC, abstractmethod

class DataSource(ABC):
    """Abstract base class for data sources: a unified read interface."""

    @abstractmethod
    def connect(self) -> None:
        """Establish a connection."""

    @abstractmethod
    def fetch(self, query: str) -> list[dict]:
        """Execute a query and return a list of results."""

    def close(self) -> None:
        """Close the connection (provides a default implementation)."""
        print("Connection closed")

class PostgresSource(DataSource):
    def connect(self) -> None:
        print("Connecting to PostgreSQL")

    def fetch(self, query: str) -> list[dict]:
        print(f"Executing: {query}")
        return [{"id": 1, "name": "Alice"}]

# DataSource()          # TypeError: cannot instantiate abstract class
source = PostgresSource()
source.connect()
print(source.fetch("SELECT * FROM users"))

Choosing a Paradigm

ScenarioRecommended Paradigm
One-off scripts, data-processing pipelinesProcedural
Data transformations, stateless logicFunctional
Complex business logic, long-lived projectsObject-Oriented
Large systemsMixed: OOP for the core domain, functional style for utility functions

The style most advocated in Python is pragmatism: no paradigm is enforced; choose whichever approach is clearest and easiest to maintain.

Last updated on