Building on intermediate OOP concepts, this lesson explores advanced object-oriented programming techniques including abstract classes, multiple inheritance, advanced magic methods, decorators, and design patterns used in professional Python development.
Abstract Classes and Interfaces
Understanding Abstract Base Classes (ABCs)
Abstract classes define a blueprint for other classes but cannot be instantiated themselves. They enforce that subclasses implement specific methods.
from abc import ABC, abstractmethod
import math
class Shape(ABC):
"""Abstract base class for geometric shapes."""
def __init__(self, name):
self.name = name
@abstractmethod
def area(self):
"""Calculate the area of the shape."""
pass
@abstractmethod
def perimeter(self):
"""Calculate the perimeter of the shape."""
pass
def describe(self):
"""Concrete method available to all subclasses."""
return f"This is a {self.name} with area {self.area():.2f} and perimeter {self.perimeter():.2f}"
class Rectangle(Shape):
"""Rectangle implementation of Shape."""
def __init__(self, width, height):
super().__init__("Rectangle")
self.width = width
self.height = height
def area(self):
"""Calculate rectangle area."""
return self.width * self.height
def perimeter(self):
"""Calculate rectangle perimeter."""
return 2 * (self.width + self.height)
class Circle(Shape):
"""Circle implementation of Shape."""
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
"""Calculate circle area."""
return math.pi * self.radius ** 2
def perimeter(self):
"""Calculate circle perimeter (circumference)."""
return 2 * math.pi * self.radius
# Using abstract classes
print("=== Abstract Classes Demo ===")
shapes = [
Rectangle(5, 3),
Circle(4),
Rectangle(10, 2)
]
for shape in shapes:
print(shape.describe())
# This would raise an error:
# shape = Shape("Generic") # TypeError: Can't instantiate abstract class
Advanced Abstract Classes with Properties
from abc import ABC, abstractmethod, abstractproperty
class Animal(ABC):
"""Abstract base class for animals."""
def __init__(self, name, species):
self.name = name
self.species = species
self._energy = 100
@abstractmethod
def make_sound(self):
"""Every animal must be able to make a sound."""
pass
@abstractmethod
def move(self):
"""Every animal must be able to move."""
pass
@property
@abstractmethod
def habitat(self):
"""Every animal has a habitat."""
pass
@property
def energy(self):
"""Get current energy level."""
return self._energy
@energy.setter
def energy(self, value):
"""Set energy level with validation."""
if 0 <= value <= 100:
self._energy = value
else:
raise ValueError("Energy must be between 0 and 100")
def sleep(self):
"""Restore energy."""
self.energy = min(100, self.energy + 20)
return f"{self.name} is sleeping and restoring energy"
class Dog(Animal):
"""Dog implementation of Animal."""
def __init__(self, name, breed):
super().__init__(name, "Canine")
self.breed = breed
def make_sound(self):
"""Dog makes a barking sound."""
self.energy -= 5
return f"{self.name} barks: Woof! Woof!"
def move(self):
"""Dog runs."""
if self.energy >= 10:
self.energy -= 10
return f"{self.name} runs around the yard"
else:
return f"{self.name} is too tired to move"
@property
def habitat(self):
"""Dog's natural habitat."""
return "Domestic environment"
def fetch(self):
"""Dog-specific method."""
if self.energy >= 15:
self.energy -= 15
return f"{self.name} fetches the ball"
else:
return f"{self.name} is too tired to fetch"
# Using the abstract animal class
print("\n=== Advanced Abstract Classes Demo ===")
dog = Dog("Buddy", "Golden Retriever")
print(dog.make_sound())
print(dog.move())
print(f"Energy: {dog.energy}")
print(f"Habitat: {dog.habitat}")
print(dog.sleep())
print(f"Energy after sleep: {dog.energy}")
print(dog.fetch())
Multiple Inheritance and Method Resolution Order (MRO)
Understanding MRO
Python uses the C3 Linearization algorithm to determine the order in which methods are resolved in multiple inheritance.
class Flyable:
"""Mixin for flying capability."""
def fly(self):
return f"{self.name} is flying high!"
def land(self):
return f"{self.name} lands safely"
class Swimmable:
"""Mixin for swimming capability."""
def swim(self):
return f"{self.name} is swimming gracefully"
def dive(self):
return f"{self.name} dives deep into the water"
class Walkable:
"""Mixin for walking capability."""
def walk(self):
return f"{self.name} is walking on land"
def run(self):
return f"{self.name} is running fast"
class Duck(Walkable, Swimmable, Flyable):
"""Duck with multiple capabilities."""
def __init__(self, name):
self.name = name
def quack(self):
return f"{self.name} quacks: Quack! Quack!"
class Penguin(Walkable, Swimmable):
"""Penguin that can walk and swim but not fly."""
def __init__(self, name):
self.name = name
def waddle(self):
return f"{self.name} waddles cutely"
# Demonstrating MRO
print("=== Multiple Inheritance Demo ===")
duck = Duck("Donald")
print(duck.quack())
print(duck.walk())
print(duck.swim())
print(duck.fly())
print(duck.dive())
print(duck.land())
penguin = Penguin("Pingu")
print(penguin.waddle())
print(penguin.swim())
print(penguin.dive())
# Check Method Resolution Order
print(f"\nDuck MRO: {Duck.__mro__}")
print(f"Penguin MRO: {Penguin.__mro__}")
# Demonstrate method resolution
print(f"Duck's walk method comes from: {Duck.__mro__[1].__name__}")
print(f"Duck's swim method comes from: {Duck.__mro__[2].__name__}")
Complex Multiple Inheritance Example
class Animal:
"""Base animal class."""
def __init__(self, name, species):
self.name = name
self.species = species
self.energy = 100
def eat(self):
self.energy = min(100, self.energy + 20)
return f"{self.name} is eating and gaining energy"
def sleep(self):
self.energy = min(100, self.energy + 30)
return f"{self.name} is sleeping and restoring energy"
class Carnivore:
"""Mixin for carnivorous animals."""
def hunt(self):
return f"{self.name} is hunting for prey"
def eat_meat(self):
return f"{self.name} is eating meat"
class Herbivore:
"""Mixin for herbivorous animals."""
def graze(self):
return f"{self.name} is grazing on plants"
def eat_plants(self):
return f"{self.name} is eating plants"
class Aquatic:
"""Mixin for aquatic animals."""
def swim(self):
return f"{self.name} is swimming in water"
def dive(self):
return f"{self.name} is diving deep"
class Terrestrial:
"""Mixin for terrestrial animals."""
def walk(self):
return f"{self.name} is walking on land"
def run(self):
return f"{self.name} is running"
class Lion(Animal, Carnivore, Terrestrial):
"""Lion - carnivorous terrestrial animal."""
def __init__(self, name):
super().__init__(name, "Lion")
def roar(self):
return f"{self.name} roars loudly!"
class Elephant(Animal, Herbivore, Terrestrial):
"""Elephant - herbivorous terrestrial animal."""
def __init__(self, name):
super().__init__(name, "Elephant")
def trumpet(self):
return f"{self.name} trumpets with its trunk!"
class Shark(Animal, Carnivore, Aquatic):
"""Shark - carnivorous aquatic animal."""
def __init__(self, name):
super().__init__(name, "Shark")
def attack(self):
return f"{self.name} attacks with its sharp teeth!"
# Using complex multiple inheritance
print("\n=== Complex Multiple Inheritance Demo ===")
lion = Lion("Simba")
print(lion.roar())
print(lion.hunt())
print(lion.walk())
print(lion.eat_meat())
elephant = Elephant("Dumbo")
print(elephant.trumpet())
print(elephant.graze())
print(elephant.walk())
print(elephant.eat_plants())
shark = Shark("Jaws")
print(shark.attack())
print(shark.hunt())
print(shark.swim())
print(shark.dive())
# Check MRO for complex inheritance
print(f"\nLion MRO: {[cls.__name__ for cls in Lion.__mro__]}")
print(f"Elephant MRO: {[cls.__name__ for cls in Elephant.__mro__]}")
print(f"Shark MRO: {[cls.__name__ for cls in Shark.__mro__]}")
Advanced Magic Methods
Context Managers with enter and exit
class DatabaseConnection:
"""Database connection with context manager support."""
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
self.is_connected = False
def __enter__(self):
"""Enter the context manager."""
print(f"Connecting to database: {self.connection_string}")
self.connection = f"Connection to {self.connection_string}"
self.is_connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit the context manager."""
if self.is_connected:
print(f"Closing connection to: {self.connection_string}")
self.connection = None
self.is_connected = False
if exc_type is not None:
print(f"Exception occurred: {exc_type.__name__}: {exc_val}")
# Return False to propagate the exception
# Return True to suppress the exception
return False
return True
def execute_query(self, query):
"""Execute a database query."""
if not self.is_connected:
raise RuntimeError("Not connected to database")
return f"Executed: {query}"
# Using context managers
print("=== Context Manager Demo ===")
try:
with DatabaseConnection("postgresql://localhost:5432/mydb") as db:
result1 = db.execute_query("SELECT * FROM users")
print(result1)
result2 = db.execute_query("INSERT INTO users VALUES (1, 'John')")
print(result2)
# This will cause an exception
# raise ValueError("Simulated database error")
except Exception as e:
print(f"Caught exception: {e}")
# Custom context manager with multiple resources
class FileProcessor:
"""File processor with context manager support."""
def __init__(self, input_file, output_file):
self.input_file = input_file
self.output_file = output_file
self.input_handle = None
self.output_handle = None
def __enter__(self):
"""Open both files."""
print(f"Opening input file: {self.input_file}")
self.input_handle = open(self.input_file, 'r')
print(f"Opening output file: {self.output_file}")
self.output_handle = open(self.output_file, 'w')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Close both files."""
if self.input_handle:
self.input_handle.close()
print(f"Closed input file: {self.input_file}")
if self.output_handle:
self.output_handle.close()
print(f"Closed output file: {self.output_file}")
if exc_type is not None:
print(f"Exception in file processing: {exc_type.__name__}")
return False # Propagate exceptions
def process_line(self, line):
"""Process a line from input file."""
processed = line.strip().upper()
self.output_handle.write(processed + '\n')
return processed
# Using file processor context manager
print("\n=== File Processor Context Manager Demo ===")
# Create test files
with open('test_input.txt', 'w') as f:
f.write("hello world\n")
f.write("python programming\n")
f.write("advanced oop\n")
try:
with FileProcessor('test_input.txt', 'test_output.txt') as processor:
with open('test_input.txt', 'r') as input_file:
for line in input_file:
processed = processor.process_line(line)
print(f"Processed: {processed}")
except Exception as e:
print(f"Error: {e}")
# Clean up test files
import os
try:
os.remove('test_input.txt')
os.remove('test_output.txt')
except FileNotFoundError:
pass
Advanced Magic Methods for Custom Collections
class CustomList:
"""Custom list implementation with advanced magic methods."""
def __init__(self, items=None):
self._items = list(items) if items else []
def __len__(self):
"""Return length of the list."""
return len(self._items)
def __getitem__(self, index):
"""Get item at index (supports slicing)."""
if isinstance(index, slice):
return CustomList(self._items[index])
return self._items[index]
def __setitem__(self, index, value):
"""Set item at index."""
self._items[index] = value
def __delitem__(self, index):
"""Delete item at index."""
del self._items[index]
def __iter__(self):
"""Return iterator."""
return iter(self._items)
def __contains__(self, item):
"""Check if item is in list."""
return item in self._items
def __add__(self, other):
"""Add two lists together."""
if isinstance(other, CustomList):
return CustomList(self._items + other._items)
elif isinstance(other, list):
return CustomList(self._items + other)
return NotImplemented
def __mul__(self, n):
"""Multiply list by a number."""
if isinstance(n, int):
return CustomList(self._items * n)
return NotImplemented
def __str__(self):
"""String representation."""
return f"CustomList({self._items})"
def __repr__(self):
"""Developer representation."""
return f"CustomList({self._items!r})"
def __bool__(self):
"""Boolean representation (empty list is False)."""
return bool(self._items)
def __eq__(self, other):
"""Check equality with another list."""
if isinstance(other, CustomList):
return self._items == other._items
elif isinstance(other, list):
return self._items == other
return False
def __lt__(self, other):
"""Less than comparison."""
if isinstance(other, (CustomList, list)):
return self._items < other._items
return NotImplemented
def __le__(self, other):
"""Less than or equal comparison."""
if isinstance(other, (CustomList, list)):
return self._items <= other._items
return NotImplemented
def __gt__(self, other):
"""Greater than comparison."""
if isinstance(other, (CustomList, list)):
return self._items > other._items
return NotImplemented
def __ge__(self, other):
"""Greater than or equal comparison."""
if isinstance(other, (CustomList, list)):
return self._items >= other._items
return NotImplemented
def append(self, item):
"""Append item to list."""
self._items.append(item)
def extend(self, items):
"""Extend list with items."""
self._items.extend(items)
def insert(self, index, item):
"""Insert item at index."""
self._items.insert(index, item)
def remove(self, item):
"""Remove first occurrence of item."""
self._items.remove(item)
def pop(self, index=-1):
"""Pop item at index."""
return self._items.pop(index)
def index(self, item):
"""Get index of item."""
return self._items.index(item)
def count(self, item):
"""Count occurrences of item."""
return self._items.count(item)
# Using the custom list
print("=== Custom List Demo ===")
# Create custom lists
list1 = CustomList([1, 2, 3, 4, 5])
list2 = CustomList([6, 7, 8])
print(f"list1: {list1}")
print(f"list2: {list2}")
print(f"Length of list1: {len(list1)}")
# Indexing and slicing
print(f"list1[0]: {list1[0]}")
print(f"list1[1:3]: {list1[1:3]}")
print(f"list1[-1]: {list1[-1]}")
# Iteration
print("Iterating over list1:")
for item in list1:
print(f" {item}")
# Membership
print(f"3 in list1: {3 in list1}")
print(f"10 in list1: {10 in list1}")
# Addition
list3 = list1 + list2
print(f"list1 + list2: {list3}")
# Multiplication
list4 = list2 * 3
print(f"list2 * 3: {list4}")
# Comparison
print(f"list1 == [1, 2, 3, 4, 5]: {list1 == [1, 2, 3, 4, 5]}")
print(f"list1 < list2: {list1 < list2}")
# Boolean conversion
empty_list = CustomList()
print(f"bool(list1): {bool(list1)}")
print(f"bool(empty_list): {bool(empty_list)}")
# List methods
list1.append(6)
print(f"After append(6): {list1}")
list1.extend([7, 8, 9])
print(f"After extend([7, 8, 9]): {list1}")
list1.insert(0, 0)
print(f"After insert(0, 0): {list1}")
list1.remove(5)
print(f"After remove(5): {list1}")
popped = list1.pop()
print(f"Popped: {popped}, list1: {list1}")
print(f"Index of 3: {list1.index(3)}")
print(f"Count of 2: {list1.count(2)}")
Class Decorators and Metaclasses
Class Decorators
def singleton(cls):
"""Class decorator that makes a class a singleton."""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
def debug_methods(cls):
"""Class decorator that adds debug logging to all methods."""
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if callable(attr) and not attr_name.startswith('_'):
def make_wrapper(func):
def wrapper(*args, **kwargs):
print(f"DEBUG: Calling {cls.__name__}.{func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"DEBUG: {cls.__name__}.{func.__name__} returned: {result}")
return result
return wrapper
setattr(cls, attr_name, make_wrapper(attr))
return cls
def validate_types(**expected_types):
"""Class decorator that validates method parameter types."""
def decorator(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
# Validate types for __init__ parameters
for i, (param_name, expected_type) in enumerate(expected_types.items()):
if i < len(args):
if not isinstance(args[i], expected_type):
raise TypeError(f"{param_name} must be of type {expected_type.__name__}")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
return decorator
# Using class decorators
@singleton
class DatabaseConnection:
"""Singleton database connection."""
def __init__(self):
self.connection_id = id(self)
print(f"Creating database connection with ID: {self.connection_id}")
def query(self, sql):
return f"Executing query with connection {self.connection_id}: {sql}"
@debug_methods
class Calculator:
"""Calculator with debug logging."""
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
@validate_types(name=str, age=int, email=str)
class Person:
"""Person with type validation."""
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __str__(self):
return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"
# Using the decorated classes
print("=== Class Decorators Demo ===")
# Singleton demo
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 is db2: {db1 is db2}")
print(f"db1.connection_id: {db1.connection_id}")
print(f"db2.connection_id: {db2.connection_id}")
# Debug methods demo
calc = Calculator()
result1 = calc.add(5, 3)
result2 = calc.multiply(4, 7)
try:
result3 = calc.divide(10, 0)
except ValueError as e:
print(f"Caught error: {e}")
# Type validation demo
try:
person1 = Person("Alice", 25, "alice@example.com")
print(f"Created person: {person1}")
person2 = Person("Bob", "30", "bob@example.com") # This will raise an error
except TypeError as e:
print(f"Type error: {e}")
Metaclasses
class SingletonMeta(type):
"""Metaclass that creates singleton classes."""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class AutoRegisterMeta(type):
"""Metaclass that automatically registers classes."""
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != 'BasePlugin':
mcs.registry[name.lower()] = cls
return cls
class ValidationMeta(type):
"""Metaclass that adds validation to class attributes."""
def __new__(mcs, name, bases, namespace):
# Add validation methods to the class
def validate_positive(self, value, attr_name):
if value <= 0:
raise ValueError(f"{attr_name} must be positive")
return value
def validate_non_empty(self, value, attr_name):
if not value or (isinstance(value, str) and not value.strip()):
raise ValueError(f"{attr_name} cannot be empty")
return value
namespace['_validate_positive'] = validate_positive
namespace['_validate_non_empty'] = validate_non_empty
return super().__new__(mcs, name, bases, namespace)
# Using metaclasses
class Logger(metaclass=SingletonMeta):
"""Singleton logger using metaclass."""
def __init__(self):
self.logs = []
def log(self, message):
self.logs.append(message)
print(f"LOG: {message}")
def get_logs(self):
return self.logs.copy()
class BasePlugin(metaclass=AutoRegisterMeta):
"""Base plugin class that auto-registers subclasses."""
def __init__(self, name):
self.name = name
def execute(self):
raise NotImplementedError("Subclasses must implement execute")
class TextPlugin(BasePlugin):
"""Text processing plugin."""
def execute(self, text):
return f"Processing text '{text}' with {self.name}"
class ImagePlugin(BasePlugin):
"""Image processing plugin."""
def execute(self, image_path):
return f"Processing image '{image_path}' with {self.name}"
class Product(metaclass=ValidationMeta):
"""Product with validation using metaclass."""
def __init__(self, name, price, quantity):
self.name = self._validate_non_empty(name, "name")
self.price = self._validate_positive(price, "price")
self.quantity = self._validate_positive(quantity, "quantity")
def __str__(self):
return f"Product(name='{self.name}', price={self.price}, quantity={self.quantity})"
# Using the metaclass-based classes
print("\n=== Metaclasses Demo ===")
# Singleton metaclass
logger1 = Logger()
logger2 = Logger()
print(f"logger1 is logger2: {logger1 is logger2}")
logger1.log("First log message")
logger2.log("Second log message")
print(f"All logs: {logger1.get_logs()}")
# Auto-register metaclass
print(f"Registered plugins: {AutoRegisterMeta.registry}")
text_plugin = TextPlugin("TextProcessor")
image_plugin = ImagePlugin("ImageProcessor")
print(text_plugin.execute("Hello World"))
print(image_plugin.execute("/path/to/image.jpg"))
# Validation metaclass
try:
product1 = Product("Laptop", 999.99, 10)
print(f"Created product: {product1}")
product2 = Product("", -100, 5) # This will raise validation errors
except ValueError as e:
print(f"Validation error: {e}")
Design Patterns in Python
Factory Pattern
from abc import ABC, abstractmethod
class Animal(ABC):
"""Abstract animal class."""
@abstractmethod
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
class Bird(Animal):
def make_sound(self):
return "Tweet!"
class AnimalFactory:
"""Factory for creating animals."""
_animals = {
'dog': Dog,
'cat': Cat,
'bird': Bird
}
@classmethod
def create_animal(cls, animal_type):
"""Create an animal of the specified type."""
animal_class = cls._animals.get(animal_type.lower())
if animal_class:
return animal_class()
else:
raise ValueError(f"Unknown animal type: {animal_type}")
@classmethod
def get_available_animals(cls):
"""Get list of available animal types."""
return list(cls._animals.keys())
# Using the factory pattern
print("=== Factory Pattern Demo ===")
factory = AnimalFactory()
print(f"Available animals: {factory.get_available_animals()}")
animals = []
for animal_type in ['dog', 'cat', 'bird']:
animal = factory.create_animal(animal_type)
animals.append(animal)
print(f"{animal_type.capitalize()}: {animal.make_sound()}")
try:
unknown = factory.create_animal('dragon')
except ValueError as e:
print(f"Error: {e}")
Observer Pattern
class Subject:
"""Subject in observer pattern."""
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
"""Attach an observer."""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer):
"""Detach an observer."""
if observer in self._observers:
self._observers.remove(observer)
def notify(self):
"""Notify all observers."""
for observer in self._observers:
observer.update(self)
def set_state(self, state):
"""Set state and notify observers."""
self._state = state
self.notify()
def get_state(self):
"""Get current state."""
return self._state
class Observer(ABC):
"""Abstract observer class."""
@abstractmethod
def update(self, subject):
pass
class ConcreteObserver(Observer):
"""Concrete observer implementation."""
def __init__(self, name):
self.name = name
self.state = None
def update(self, subject):
"""Update when subject changes."""
self.state = subject.get_state()
print(f"{self.name} received update: {self.state}")
# Using the observer pattern
print("\n=== Observer Pattern Demo ===")
subject = Subject()
observer1 = ConcreteObserver("Observer 1")
observer2 = ConcreteObserver("Observer 2")
observer3 = ConcreteObserver("Observer 3")
subject.attach(observer1)
subject.attach(observer2)
subject.attach(observer3)
subject.set_state("State 1")
subject.set_state("State 2")
subject.detach(observer2)
subject.set_state("State 3")
Strategy Pattern
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
"""Abstract payment strategy."""
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
"""Credit card payment strategy."""
def __init__(self, card_number, cvv):
self.card_number = card_number
self.cvv = cvv
def pay(self, amount):
return f"Paid ${amount} using Credit Card ending in {self.card_number[-4:]}"
class PayPalPayment(PaymentStrategy):
"""PayPal payment strategy."""
def __init__(self, email):
self.email = email
def pay(self, amount):
return f"Paid ${amount} using PayPal account {self.email}"
class BankTransferPayment(PaymentStrategy):
"""Bank transfer payment strategy."""
def __init__(self, account_number):
self.account_number = account_number
def pay(self, amount):
return f"Paid ${amount} using Bank Transfer to account {self.account_number}"
class PaymentProcessor:
"""Payment processor using strategy pattern."""
def __init__(self):
self.payment_strategy = None
def set_payment_strategy(self, strategy):
"""Set the payment strategy."""
self.payment_strategy = strategy
def process_payment(self, amount):
"""Process payment using current strategy."""
if self.payment_strategy is None:
raise ValueError("No payment strategy set")
return self.payment_strategy.pay(amount)
# Using the strategy pattern
print("\n=== Strategy Pattern Demo ===")
processor = PaymentProcessor()
# Credit card payment
processor.set_payment_strategy(CreditCardPayment("1234567890123456", "123"))
print(processor.process_payment(100.50))
# PayPal payment
processor.set_payment_strategy(PayPalPayment("user@example.com"))
print(processor.process_payment(75.25))
# Bank transfer payment
processor.set_payment_strategy(BankTransferPayment("9876543210"))
print(processor.process_payment(200.00))
Key Takeaways
- Abstract classes enforce contracts and provide common interfaces
- Multiple inheritance requires understanding of Method Resolution Order (MRO)
- Advanced magic methods enable custom behavior for built-in operations
- Context managers provide safe resource management
- Class decorators modify class behavior without inheritance
- Metaclasses control class creation and behavior
- Design patterns provide proven solutions to common problems
- Professional OOP involves proper abstraction and encapsulation
Next Steps
In the next lesson, we'll explore Functional Programming - higher-order functions, closures, decorators, and functional programming paradigms that make Python code more elegant and efficient.