Object-Oriented Programming
Object-Oriented Programming (OOP) is a way to organize code by grouping data (attributes) and behavior (methods) together inside objects. Instead of writing separate functions and variables, you design small “models” of real-world or logical entities. This approach makes code easier to understand, reuse, and scale, especially in large applications like APIs, frameworks, and libraries.
Main Ideas
Section titled “Main Ideas”| Concept | Meaning |
|---|---|
| Class | Blueprint used to create objects |
| Object | Instance of a class |
| Attribute | Data stored inside object/class |
| Method | Function defined inside a class |
| Encapsulation | Binding data + behavior together |
| Inheritance | Reusing another class |
| Polymorphism | Same method name, different behavior |
| Abstraction | Hiding complexity, exposing essentials |
Each of these concepts builds on the idea that we model “things” instead of writing scattered logic.
Classes and Objects
Section titled “Classes and Objects”A class defines structure and behavior, while an object is a real usable instance of that class. When you create an object, Python allocates memory and stores its state separately from other objects.
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.balance = balance
def deposit(self, amount): self.balance += amount
def summary(self): return f"{self.owner}: {self.balance}"
account = BankAccount("Asha", 1000)account.deposit(250)print(account.summary())Explanation:
BankAccountis the blueprint.accountis a real object with its own data.- Methods operate on that object’s data.
Namespace and Attribute Lookup
Section titled “Namespace and Attribute Lookup”A namespace is a mapping of names to values. In OOP, two main namespaces matter: instance (object) namespace and class namespace. Python always searches for attributes in a specific order.
class Tea: origin = "India"
masala = Tea()masala.origin = "Nepal"Explanation:
Tea.origin→ class namespacemasala.origin→ instance namespace (after assignment)- Instance value overrides class value
Lookup Order:
Python first checks object → then class → then built-in.
Attribute Shadowing
Section titled “Attribute Shadowing”Attribute shadowing happens when an instance variable hides a class variable with the same name. This does not delete the class variable, it just overrides it for that object.
class Tea: temperature = "hot"
cup = Tea()print(cup.temperature) # hot
cup.temperature = "warm"print(cup.temperature) # warmprint(Tea.temperature) # hot
del cup.temperatureprint(cup.temperature) # hot againExplanation:
- Instance attribute has higher priority
- Removing it restores class attribute visibility
self represents the current object instance. It allows each object to store its own data separately. Without self, all objects would share the same variables.
class Cup: def __init__(self, size): self.size = size
def describe(self): return f"A {self.size}ml cup"Explanation:
self.sizebelongs to that specific objectselfis passed automatically when callingcup.describe()
Constructor
Section titled “Constructor”__init__ is automatically called when an object is created. It initializes the object’s state.
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.balance = balanceExplanation:
- It sets initial values
- Every object starts with defined state
- Not required, but commonly used
Inheritance and Composition
Section titled “Inheritance and Composition”Inheritance
Section titled “Inheritance”Inheritance allows one class (child) to reuse another class (parent). It helps avoid code duplication.
class Account: def describe(self): return "Base account"
class SavingsAccount(Account): def withdraw(self, amount): return f"Withdrawing {amount}"Explanation:
SavingsAccountgetsdescribe()automatically- Extends behavior without rewriting code
Composition
Section titled “Composition”Composition means building a class using other objects instead of inheriting.
class StatementPrinter: def print_summary(self, account): return account.describe()
class Bank: def __init__(self): self.printer = StatementPrinter()Explanation:
BankusesStatementPrinter- More flexible than inheritance
- Easy to replace components
Calling the Base Class
Section titled “Calling the Base Class”When child class needs parent initialization, use super().
class BaseAccount: def __init__(self, owner, balance): self.owner = owner self.balance = balance
class SuperAccount(BaseAccount): def __init__(self, owner, balance): super().__init__(owner, balance)Explanation:
super()calls parent method- Required in inheritance chains
- Prevents duplication and errors
MRO (Method Resolution Order)
Section titled “MRO (Method Resolution Order)”MRO defines the order Python follows to search for methods in multiple inheritance.
class A: passclass B(A): passclass C(A): passclass D(C, B): pass
print(D.__mro__)Output:
D → C → B → A → objectExplanation:
- Python uses C3 linearization
- Ensures consistent and predictable lookup
Static Methods and Class Methods
Section titled “Static Methods and Class Methods”Static Method
Section titled “Static Method”A static method does not use instance or class data. It behaves like a normal function but is inside a class.
class TextTools: @staticmethod def clean(text): return [item.strip() for item in text.split(",")]Explanation:
- No
selforcls - Used for utility functions
Class Method
Section titled “Class Method”A class method receives the class (cls) instead of instance.
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.balance = balance
@classmethod def from_string(cls, text): owner, balance = text.split("-") return cls(owner, int(balance))Explanation:
- Used for alternative constructors
- Helps in building libraries (important)
Why class methods are important in libraries
Section titled “Why class methods are important in libraries”When building libraries:
- You don’t want users to always call
__init__manually - You provide clean ways to create objects
Example:
user = User.from_json(data)user = User.from_db(row)This improves:
- readability
- flexibility
- maintainability
Private Attributes and Properties
Section titled “Private Attributes and Properties”Python does not enforce strict privacy but uses naming conventions.
_name→ internal use__name→ name mangling
Properties allow controlled access.
class Account: def __init__(self, name): self._name = name self._balance = 0
@property def balance(self): return self._balance
@balance.setter def balance(self, amount): if amount >= 0: self._balance = amountExplanation:
- Access like attribute, but logic runs
- Ensures validation
Overriding and Overloading
Section titled “Overriding and Overloading”Overriding
Section titled “Overriding”Child class replaces parent method.
class A: def message(self): return "A message"
class B(A): def message(self): return "B message"Explanation:
- Same method name
- Different behavior
Overloading
Section titled “Overloading”Python does not support true overloading, but uses defaults.
def add(a, b=0, c=0): return a + b + cExplanation:
- One function handles multiple cases
Deprecating Methods
Section titled “Deprecating Methods”from warnings import deprecated
class API: @deprecated("Use new_function() instead") def old_method(self): # this warrning is optional, but helps users know about deprecation and encourages them to switch to the new method warnings.warn( "old_method is deprecated, use new_method instead", DeprecationWarning, stacklevel=2 )
def new_method(self): return "new"Explanation:
- Warns users without breaking code
- Common in frameworks and libraries
- Helps safe upgrades
Best practice:
- Keep old method temporarily
- Add warning
- Remove later
Destructor
Section titled “Destructor”__del__ is called when an object is about to be destroyed.
class Test: def __del__(self): print("Object destroyed")Explanation:
- Not reliable for important cleanup
- Python garbage collection timing is unpredictable