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 unchangedObject-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)) # 5The 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
| Scenario | Recommended Paradigm |
|---|---|
| One-off scripts, data-processing pipelines | Procedural |
| Data transformations, stateless logic | Functional |
| Complex business logic, long-lived projects | Object-Oriented |
| Large systems | Mixed: 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.