ADVANCED LEVEL
MODERN PATTERNS
PRODUCTION READY

Advanced Python OOP Patterns Modern Design Patterns for 2026

Master professional Python development with modern design patterns, performance optimizations, and architectural patterns used by top tech companies. Learn Protocols, dataclasses with slots, dependency injection, and more.

Protocols: Structural Subtyping (PEP 544)

Protocols provide structural subtyping (static duck typing) in Python, enabling flexible, type-safe interfaces without inheritance hierarchies.

Protocol Example: Database Connection

from typing import Protocol, Any, List, Dict, Optional from dataclasses import dataclass import json class DatabaseProtocol(Protocol): """Protocol defining database operations.""" def connect(self) -> None: """Establish database connection.""" ... def execute(self, query: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: """Execute SQL query and return results.""" ... def close(self) -> None: """Close database connection.""" ... class PostgresDatabase: """PostgreSQL implementation.""" def connect(self) -> None: print("Connecting to PostgreSQL...") def execute(self, query: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: return [{"id": 1, "name": "John"}] def close(self) -> None: print("Closing PostgreSQL connection...") class SQLiteDatabase: """SQLite implementation.""" def connect(self) -> None: print("Connecting to SQLite...") def execute(self, query: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: return [{"id": 1, "name": "Jane"}] def close(self) -> None: print("Closing SQLite connection...") def run_database_operations(db: DatabaseProtocol) -> List[Dict[str, Any]]: """Works with any database implementing the protocol.""" db.connect() result = db.execute("SELECT * FROM users", {}) db.close() return result # Usage postgres_db = PostgresDatabase() sqlite_db = SQLiteDatabase() # Both work because they implement the protocol run_database_operations(postgres_db) run_database_operations(sqlite_db)

When to Use Protocols

Use Protocols when you need flexible interfaces without tight coupling, for dependency injection, testing (mocking), and supporting multiple implementations.

When to Avoid Protocols

Avoid Protocols when you need runtime type checking, when working with legacy code that doesn't support type hints, or when inheritance hierarchies are necessary.

Dataclasses with __slots__

Combine dataclasses with __slots__ for memory efficiency and performance optimization in high-performance applications.

Dataclass with Slots Example

from dataclasses import dataclass, field from typing import List, Optional import sys class UserRegular: """Regular class for comparison.""" def __init__( self, user_id: int, username: str, email: str, roles: List[str], is_active: bool = True ): self.user_id = user_id self.username = username self.email = email self.roles = roles self.is_active = is_active @dataclass class UserDataclass: """Dataclass without slots.""" user_id: int username: str email: str roles: List[str] = field(default_factory=list) is_active: bool = True @dataclass class UserSlots: """Dataclass with slots for memory efficiency.""" __slots__ = ['user_id', 'username', 'email', 'roles', 'is_active'] user_id: int username: str email: str roles: List[str] is_active: bool = True def __init__( self, user_id: int, username: str, email: str, roles: List[str] = None, is_active: bool = True ): self.user_id = user_id self.username = username self.email = email self.roles = roles or [] self.is_active = is_active def __repr__(self) -> str: return ( f"UserSlots(user_id={self.user_id}, " f"username='{self.username}', email='{self.email}')" ) # Memory comparison regular_user = UserRegular(1, "john_doe", "john@example.com", ["admin", "user"]) dataclass_user = UserDataclass(1, "john_doe", "john@example.com", ["admin", "user"]) slots_user = UserSlots(1, "john_doe", "john@example.com", ["admin", "user"]) print(f"Regular class memory: {sys.getsizeof(regular_user)} bytes") print(f"Dataclass memory: {sys.getsizeof(dataclass_user)} bytes") print(f"Slots memory: {sys.getsizeof(slots_user)} bytes")
Feature Regular Class Dataclass Dataclass + Slots
Memory Usage High Medium-High Low
Attribute Access Speed Slow Fast Fastest
Dynamic Attributes Yes Yes No
Boilerplate Code High Low Low
Inheritance Support Full Full Limited

Modern Design Patterns

Essential design patterns adapted for Python's dynamic nature and modern ecosystem.

Dependency Injection

Inject dependencies at runtime for testable, flexible, and maintainable code without tight coupling.

  • Constructor injection for explicit dependencies
  • Protocol-based interfaces
  • Container management with injector library
  • Mocking for unit testing
  • Configuration-driven dependencies
class PaymentProcessor: def __init__(self, gateway: PaymentGateway): self.gateway = gateway def process(self, amount: float): return self.gateway.charge(amount)

Repository Pattern

Abstract data access layer to separate business logic from data storage concerns.

  • Generic repositories with type parameters
  • Async/await support for database operations
  • Unit of Work pattern integration
  • Multiple database backend support
  • Caching layer integration
class UserRepository: async def get_by_id(self, id: int) -> Optional[User]: async def save(self, user: User): async def delete(self, id: int):

Factory Pattern

Create objects without exposing instantiation logic, using modern Python features.

  • Factory functions with type hints
  • Class methods as alternative constructors
  • Registry pattern for plugin systems
  • Dependency injection integration
  • Configuration-based object creation
@dataclass class NotificationFactory: def create(self, type: str) -> Notification: if type == "email": return EmailNotification() elif type == "sms": return SMSNotification()

Service Layer

Organize business logic into services that coordinate between repositories and other services.

  • Transaction management
  • Error handling and logging
  • Async operation coordination
  • Validation and business rules
  • Event publishing integration
class UserService: def __init__(self, repo: UserRepository): self.repo = repo async def register(self, user_data: UserCreate): # Business logic here user = User(**user_data.dict()) await self.repo.save(user)

Best Practices for Advanced Python OOP

1. Prefer Composition Over Inheritance

Use composition and dependency injection instead of deep inheritance hierarchies. This creates more flexible, testable, and maintainable code.

2. Use Type Hints Consistently

Always add type hints to public APIs, function signatures, and class definitions. Use Protocols for interfaces and Generics for container classes.

3. Implement __slots__ for Performance

Use __slots__ in classes that will have many instances to reduce memory usage and improve attribute access speed by 20-40%.

4. Separate Concerns with Patterns

Use Repository pattern for data access, Service pattern for business logic, and Factory pattern for object creation to maintain clean architecture.

5. Design for Testability

Write classes that are easy to test by injecting dependencies, avoiding global state, and using interfaces (Protocols) that can be mocked.

6. Use Context Managers

Implement __enter__ and __exit__ methods for resource management (files, database connections, locks) to ensure proper cleanup.

7. Embrace Async/Await Patterns

Design classes that can work in async contexts when dealing with I/O operations. Use async __aenter__ and __aexit__ for async context managers.