Programming Paradigms and Python OOP
Programming paradigms are the fundamental styles or approaches used to solve problems with code. Each paradigm provides a distinct way of thinking about program structure, data flow, and…

In Python's OOP model, what is the role of the 'self' parameter in methods?
Which of the following is a dunder method that customizes the string representation of an object?
A class that groups data and functions together is primarily associated with which programming paradigm?
If a Python class defines __add__(self, other), what operation does this enable between two instances of the class?
Which paradigm is characterized by breaking a program into functions or procedures called subprograms?
In the OOP context, what does the term 'interface' refer to?
Which of the following statements about Python's __len__ dunder method is correct?
When a Python class defines both __init__ and __repr__, what is the typical purpose of each?
Which paradigm describes problems as a set of facts and rules from which other facts are deduced?
Understanding Programming Paradigms
Programming paradigms are the fundamental styles or approaches used to solve problems with code. Each paradigm provides a distinct way of thinking about program structure, data flow, and control mechanisms. In this course we will explore the most common paradigms—declarative, procedural, and object‑oriented—while linking them to concrete Python examples.
Declarative vs. Imperative Programming
Declarative programming focuses on what the result should be, leaving the how to the underlying engine. SQL queries and HTML markup are classic examples: you describe the desired data or layout, and the database or browser decides the execution steps.
In contrast, imperative (or procedural) programming requires you to spell out each step. Languages such as C, Java, and even Python when used in a classic way follow this model. Understanding the difference helps you choose the right tool for a given task.
- Declarative: "Select all customers from Italy" – the database decides the optimal plan.
- Imperative: "Loop through the list, check each element, and add it to a new list" – you write the loop yourself.
Procedural (Structured) Paradigm
The procedural paradigm breaks a program into functions or procedures. Each subprogram performs a specific task and can be called from many places, promoting reuse and readability. In Python, functions are first‑class objects, making procedural design straightforward.
Typical characteristics:
- Linear flow of control (calls, returns).
- Local variables scoped to functions.
- Clear entry and exit points.
Procedural code is often the stepping stone toward more advanced paradigms like object‑oriented programming.
Object‑Oriented Programming (OOP) in Python
Object‑oriented programming groups data and behavior into classes. A class defines a blueprint; an object (or instance) is a concrete manifestation of that blueprint. Python’s OOP model is dynamic and flexible, allowing you to create rich abstractions with relatively little boilerplate.
The Role of self
Every instance method in a Python class receives self as its first parameter. This variable is a reference to the specific object on which the method is invoked. It enables the method to access or modify the object's attributes and to call other methods of the same instance.
Example:
class Counter:
def __init__(self, start=0):
self.value = start
def increment(self):
self.value += 1
def display(self):
print(f"Current count: {self.value}")
c = Counter(5)
c.increment() # self refers to c inside increment()
c.display() # prints "Current count: 6"
Without self, the method would have no way to know which object's value to modify.
Dunder (Double‑Underscore) Methods
Python reserves method names that start and end with double underscores—commonly called dunder methods—to provide special behavior for built‑in operations. By overriding these methods, you can make your objects behave like native Python types.
__init__: Object initialization (constructor).__repr__: Official string representation, used byrepr()and in the interactive console.__add__: Defines behavior for the+operator.__len__: Determines the result oflen()on the object.
These methods are essential for creating intuitive, Pythonic APIs.
Customizing String Representation with __repr__
The __repr__ method should return a string that, if possible, could be used to recreate the object. It is also the fallback for str() when __str__ is not defined.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(2, 3)
print(p) # Output: Point(2, 3)
Providing a clear __repr__ aids debugging and improves the developer experience.
Operator Overloading with __add__
When a class defines __add__(self, other), instances of that class can be added using the + operator. This is known as operator overloading and allows you to write expressive code that mirrors natural language.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Output: Vector(4, 6)
Here, v1 + v2 triggers v1.__add__(v2), returning a new Vector instance.
Implementing __len__
The __len__ dunder method tells Python how to compute the length of an object with the built‑in len() function. It is commonly used in custom container classes.
class Bag:
def __init__(self, items=None):
self.items = items or []
def __len__(self):
return len(self.items)
b = Bag(['apple', 'banana', 'cherry'])
print(len(b)) # Output: 3
By defining __len__, your class integrates seamlessly with Python’s standard library.
Understanding Interfaces in OOP
In Python, an interface is not a formal language construct (as it is in Java), but rather a conceptual contract: the set of methods an object promises to provide. When you write code that expects an object with a particular method signature, you are relying on its interface.
Example of a simple interface via abstract base class:
from abc import ABC, abstractmethod
class Drawable(ABC):
@abstractmethod
def draw(self):
pass
class Circle(Drawable):
def draw(self):
print('Drawing a circle')
Any class that implements draw adheres to the Drawable interface, allowing polymorphic use.
Connecting Paradigms to Real‑World Scenarios
Choosing the right paradigm can dramatically affect code maintainability, performance, and readability. Below are typical scenarios and the paradigm that best fits them.
- Data analysis pipelines – often declarative (e.g., SQL, pandas query syntax) because you describe the desired transformation.
- Utility scripts – procedural style works well for linear, step‑by‑step tasks.
- Large applications with many interacting entities – object‑oriented design helps encapsulate state and behavior.
- Event‑driven GUIs – combine OOP (widgets as objects) with event‑driven callbacks.
Key Takeaways
- Declarative programming specifies what you want; the system decides how.
- Procedural programming structures code into functions or procedures.
- Object‑oriented programming groups data and behavior into classes, using
selfto reference the current instance. - Dunder methods (
__repr__,__add__,__len__, etc.) let you customize how objects interact with Python’s built‑in operations. - An interface is the collection of methods an object provides, enabling polymorphism.
Frequently Asked Questions (FAQ)
What is the difference between __repr__ and __str__?
__repr__ aims for an unambiguous representation, often usable for recreation, while __str__ provides a readable, user‑friendly version. If only __repr__ is defined, Python falls back to it for str().
Can I mix paradigms in a single Python project?
Absolutely. Python’s flexibility encourages hybrid designs: you might write core algorithms procedurally, expose them through OOP classes, and use declarative libraries for configuration or data querying.
Do I always need to define self in methods?
Instance methods require self as the first parameter. Class methods use cls, and static methods omit both, but they cannot access instance attributes.
Further Reading and Resources
- Python Official Documentation – Classes
- Real Python – Understanding Dunder Methods
- Wikipedia – Declarative Programming
- TutorialsPoint – Python OOP
