Dependency Injection (DI) in Python is a design pattern where the dependencies of a class or function are provided (injected) from the outside, rather than being created or managed by the class or function itself. This approach makes the code more modular, testable, and easier to maintain.
Key Concepts
Dependency: Any external object or resource that a class or function needs to operate (e.g., a database connection, an API client, a logger).
Injection: Supplying the dependency from outside the class or function, typically as an argument.
Why Use Dependency Injection?
Decoupling: Reduces tight coupling between components.
Testability: Makes it easier to test classes or functions by providing mock or stub dependencies.
Flexibility: Allows swapping out dependencies without modifying the dependent class or function.
Example Without Dependency Injection
Here, the dependency (Logger) is created inside the Service class, which tightly couples the two.
class Logger:
def log(self, message):
print(f"LOG: {message}")
class Service:
def __init__(self):
self.logger = Logger() # Dependency is created inside the class
def perform_task(self):
self.logger.log("Task performed")
Example With Dependency Injection
In this example, the Logger is injected into the Service class, making the Service independent of how the Logger is implemented.
python
Copy code
class Logger:
def log(self, message):
print(f"LOG: {message}")
class Service:
def __init__(self, logger): # Dependency is injected
self.logger = logger
def perform_task(self):
self.logger.log("Task performed")
# Injecting the dependency
logger = Logger()
service = Service(logger)
service.perform_task()
Benefits
The Service class does not need to know how the Logger is implemented.
You can easily swap out Logger for another implementation (e.g., FileLogger, DatabaseLogger) without modifying Service.
Dependency Injection with Frameworks
In larger applications, dependency injection frameworks (like Dependency Injector or pytest fixtures in testing) can help manage dependencies systematically.
from dependency_injector import containers, providers
class Logger:
def log(self, message):
print(f"LOG: {message}")
class Service:
def __init__(self, logger):
self.logger = logger
def perform_task(self):
self.logger.log("Task performed")
# DI container
class Container(containers.DeclarativeContainer):
logger = providers.Factory(Logger)
service = providers.Factory(Service, logger=logger)
# Using the container
container = Container()
service = container.service()
service.perform_task()
1. Providers
Providers are objects responsible for creating and managing dependencies. They can define how objects (dependencies) are created and supply these objects to other parts of the application.
Providers can:
Instantiate objects.
Return pre-configured instances.
Manage singletons (single instances reused across the application).
Provide factories for creating new instances on demand.
Types of Providers
Here are some commonly used provider types in Dependency Injector:
Factory: Creates a new instance of an object every time it's called.
Singleton: Creates and returns the same instance for every call.
Callable: Calls a specified function or callable.
Configuration: Provides values from an external configuration source (e.g., environment variables, files).
Delegate: Delegates provisioning to another provider.
Resource: Manages external resources with lifecycle hooks like initialization and cleanup
from dependency_injector import providers
# Factory provider (creates a new instance each time)
class Logger:
def log(self, message):
print(f"LOG: {message}")
logger_provider = providers.Factory(Logger)
logger_instance_1 = logger_provider()
logger_instance_2 = logger_provider()
print(logger_instance_1 is logger_instance_2) # False (new instance each time)
# Singleton provider (creates one instance only)
singleton_logger = providers.Singleton(Logger)
logger_instance_3 = singleton_logger()
logger_instance_4 = singleton_logger()
print(logger_instance_3 is logger_instance_4) # True (same instance)
No comments:
Post a Comment