什么是Pythonic方式进行依赖注入?

bag*_*rat 63 python oop authentication frameworks dependency-injection

介绍

对于Java,依赖注入作为纯OOP工作,即您提供要实现的接口,并在您的框架代码中接受实现已定义接口的类的实例.

现在对于Python,您可以以相同的方式执行,但我认为在Python的情况下,该方法的开销太大了.那么你将如何以Pythonic方式实现它?

用例

说这是框架代码:

class FrameworkClass():
    def __init__(self, ...):
        ...

    def do_the_job(self, ...):
        # some stuff
        # depending on some external function
Run Code Online (Sandbox Code Playgroud)

基本方法

最天真(也许是最好的?)方式是要求将外部函数提供给FrameworkClass构造函数,然后从该do_the_job方法调用.

框架代码:

class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self, ...):
        # some stuff
        self.func(...)
Run Code Online (Sandbox Code Playgroud)

客户代码:

def my_func():
    # my implementation

framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Run Code Online (Sandbox Code Playgroud)

问题很简短.有没有更好的常用Pythonic方法来做到这一点?或者也许任何支持此类功能的库?

更新:具体情况

想象一下,我开发了一个微型Web框架,它使用令牌处理身份验证.该框架需要一个函数来提供ID从令牌获得的一些并获得与之对应的用户ID.

显然,框架对用户或任何其他特定于应用程序的逻辑一无所知,因此客户端代码必须将用户getter功能注入框架以使身份验证工作.

小智 53

Raymond Hettinger - 超级考虑超级!- PyCon 2015关于如何使用超级和多重继承而不是DI的论点.如果您没有时间观看整个视频,请跳至第15分钟(但我建议您观看所有视频).

以下是如何将此视频中描述的内容应用于您的示例的示例:

框架代码:

class TokenInterface():
    def getUserFromToken(self, token):
        raise NotImplementedError

class FrameworkClass(TokenInterface):
    def do_the_job(self, ...):
        # some stuff
        self.user = super().getUserFromToken(...)
Run Code Online (Sandbox Code Playgroud)

客户代码:

class SQLUserFromToken(TokenInterface):
    def getUserFromToken(self, token):      
        # load the user from the database
        return user

class ClientFrameworkClass(FrameworkClass, SQLUserFromToken):
    pass

framework_instance = ClientFrameworkClass()
framework_instance.do_the_job(...)
Run Code Online (Sandbox Code Playgroud)

这将起作用,因为Python MRO将保证调用getUserFromToken客户端方法(如果使用了super()).如果您使用的是Python 2.x,代码将不得不更改.

这里的一个额外好处是,如果客户端不提供实现,这将引发异常.

当然,这不是真正的依赖注入,它是多重继承和mixins,但它是一种解决问题的Pythonic方法.

  • 这个答案被认为是`super()`:) (8认同)
  • DI 的优点之一是我可以在测试场景中注入其他依赖项。这种方法如何处理该用例? (4认同)
  • 虽然我觉得它很优雅,但是我使用这种方法有两个问题:1.当您需要将服务器项注入到您的类中时会发生什么?2.继承最经常用在“是” /专业化意义上。将其用于DI违反了这个想法(例如,如果我想将服务注入到Presenter中)。 (3认同)
  • Raymond称它为CI,而我认为,它是纯粹的混合物.但是,在Python中,mixin和CI几乎是一样的吗?唯一的区别是不确定程度.Mixin将依赖项注入类级别,而CI将依赖项注入实例. (2认同)
  • 使用这种方法,如何为 FrameworkClass 编写单元测试,为 TokenInterface 提供模拟? (2认同)

Pio*_*rek 17

我们在项目中进行依赖注入的方式是使用inject lib.查看文档.我强烈建议将它用于DI.只需要一个函数就没有意义,但是当你必须管理多个数据源等时开始有意义.

按照你的例子,它可能类似于:

# framework.py
class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self):
        # some stuff
        self.func()
Run Code Online (Sandbox Code Playgroud)

你的自定义功能:

# my_stuff.py
def my_func():
    print('aww yiss')
Run Code Online (Sandbox Code Playgroud)

在应用程序的某个位置,您要创建一个跟踪所有已定义依赖项的引导文件:

# bootstrap.py
import inject
from .my_stuff import my_func

def configure_injection(binder):
    binder.bind(FrameworkClass, FrameworkClass(my_func))

inject.configure(configure_injection)
Run Code Online (Sandbox Code Playgroud)

然后你可以这样使用代码:

# some_module.py (has to be loaded with bootstrap.py already loaded somewhere in your app)
import inject
from .framework import FrameworkClass

framework_instance = inject.instance(FrameworkClass)
framework_instance.do_the_job()
Run Code Online (Sandbox Code Playgroud)

我担心这会像pythonic一样(模块有一些python的甜味,像装饰器通过参数注入等 - 检查文档),因为python没有像接口或类型提示这样的花哨的东西.

所以直接回答你的问题会非常困难.我认为真正的问题是:python是否对DI有一些原生支持?答案是,遗憾的是:不.


Bry*_*ach 17

依赖注入是 Python 直接支持的一种简单技术。不需要额外的库。使用类型提示可以提高清晰度和可读性。

框架代码:

class UserStore():
    """
    The base class for accessing a user's information.
    The client must extend this class and implement its methods.
    """
    def get_name(self, token):
        raise NotImplementedError

class WebFramework():
    def __init__(self, user_store: UserStore):
        self.user_store = user_store

    def greet_user(self, token):
        user_name = self.user_store.get_name(token)
        print(f'Good day to you, {user_name}!')
Run Code Online (Sandbox Code Playgroud)

客户端代码:

class AlwaysMaryUser(UserStore):
    def get_name(self, token):      
        return 'Mary'

class SQLUserStore(UserStore):
    def __init__(self, db_params):
        self.db_params = db_params

    def get_name(self, token):
        # TODO: Implement the database lookup
        raise NotImplementedError

client = WebFramework(AlwaysMaryUser())
client.greet_user('user_token')
Run Code Online (Sandbox Code Playgroud)

UserStore实现依赖注入不需要类和类型提示。他们的主要目的是为客户端开发人员提供指导。如果删除该类UserStore及其所有引用,该代码仍然有效。


Rom*_*tov 7

前段时间我编写了依赖注入微框架,其目标是使它成为Pythonic - Dependency Injector.这就是你的代码在使用时的样子:

"""Example of dependency injection in Python."""

import logging
import sqlite3

import boto.s3.connection

import example.main
import example.services

import dependency_injector.containers as containers
import dependency_injector.providers as providers


class Platform(containers.DeclarativeContainer):
    """IoC container of platform service providers."""

    logger = providers.Singleton(logging.Logger, name='example')

    database = providers.Singleton(sqlite3.connect, ':memory:')

    s3 = providers.Singleton(boto.s3.connection.S3Connection,
                             aws_access_key_id='KEY',
                             aws_secret_access_key='SECRET')


class Services(containers.DeclarativeContainer):
    """IoC container of business service providers."""

    users = providers.Factory(example.services.UsersService,
                              logger=Platform.logger,
                              db=Platform.database)

    auth = providers.Factory(example.services.AuthService,
                             logger=Platform.logger,
                             db=Platform.database,
                             token_ttl=3600)

    photos = providers.Factory(example.services.PhotosService,
                               logger=Platform.logger,
                               db=Platform.database,
                               s3=Platform.s3)


class Application(containers.DeclarativeContainer):
    """IoC container of application component providers."""

    main = providers.Callable(example.main.main,
                              users_service=Services.users,
                              auth_service=Services.auth,
                              photos_service=Services.photos)
Run Code Online (Sandbox Code Playgroud)

以下是此示例的更广泛描述的链接 - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html

希望它可以帮助一点.欲了解更多信息,请访问:

  • 你好@BillDeRose.虽然我的答案被认为太长了,不能作为SO评论,但我已经创建了一个github问题并在那里发布我的答案 - https://github.com/ets-labs/python-dependency-injector/issues/197 :)希望它有所帮助,谢谢,罗马 (2认同)

小智 5

在尝试了 Python 中的一些 DI 框架之后,我发现与其他领域(例如 .NET Core)中的简单性相比,它们使用起来有点笨拙。这主要是由于通过诸如装饰器之类的东西进行连接,这些东西使代码变得混乱,使得很难简单地将其添加到项目中或从项目中删除它,或者基于变量名称进行连接。

我最近一直在开发一个依赖注入框架,它使用类型注释来进行注入,称为简单注入。下面是一个简单的例子

from simple_injection import ServiceCollection


class Dependency:
    def hello(self):
        print("Hello from Dependency!")

class Service:
    def __init__(self, dependency: Dependency):
        self._dependency = dependency

    def hello(self):
        self._dependency.hello()

collection = ServiceCollection()
collection.add_transient(Dependency)
collection.add_transient(Service)

collection.resolve(Service).hello()
# Outputs: Hello from Dependency!
Run Code Online (Sandbox Code Playgroud)

该库支持服务生命周期并将服务绑定到实现。

该库的目标之一是,可以轻松地将其添加到现有应用程序中,并在提交之前了解您的喜好,因为它所需要的只是您的应用程序具有适当的类型,然后您可以在以下位置构建依赖关系图:入口点并运行它。

希望这可以帮助。欲了解更多信息,请参阅