Saturday, December 21, 2024

What is dependency Injection in Python

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