代码之家  ›  专栏  ›  技术社区  ›  Konstantin Spirin

如何进行依赖注入python方式?

  •  12
  • Konstantin Spirin  · 技术社区  · 15 年前

    最近我读了很多关于python的文章,所以我的问题是

    如何进行依赖注入python方式?

    例如,当服务A需要访问UserService进行授权检查时,我将讨论常见的场景。

    4 回复  |  直到 7 年前
        1
  •  14
  •   Thomas Wouters    15 年前

    这完全取决于形势。例如,如果您将依赖注入用于测试目的——这样您就可以轻松地模拟出一些东西——您通常可以完全放弃注入:您可以模拟出您将要注入的模块或类:

    subprocess.Popen = some_mock_Popen
    result = subprocess.call(...)
    assert some_mock_popen.result == result
    

    subprocess.call() 会打电话的 subprocess.Popen() 我们可以模拟它,而不必以特殊的方式注入依赖关系。我们可以换一个 subprocess.Popen 直接。(这只是一个例子;在现实生活中,你会以一种更强大的方式来做这件事。)

    如果在更复杂的情况下使用依赖注入,或者当模拟整个模块或类不合适时(例如,因为您只想模拟一个特定的调用),那么通常选择使用类属性或模块全局作为依赖项。例如,考虑 my_subprocess.py :

    from subprocess import Popen
    
    def my_call(...):
        return Popen(...).communicate()
    

    您可以很容易地替换 Popen 呼叫人 my_call() 通过分配给 my_subprocess.Popen 不会影响任何其他呼叫 子进程.popen (但它将取代所有对 my_子进程.popen 当然。)同样,类属性:

    class MyClass(object):
        Popen = staticmethod(subprocess.Popen)
        def call(self):
            return self.Popen(...).communicate(...)
    

    当使用这样的类属性时,考虑到选项,这是很少必要的,您应该注意使用 staticmethod . 如果不这样做,并且要插入的对象是一个普通的函数对象或另一种类型的描述符,比如属性,当从类或实例中检索时,它会做一些特殊的事情,这会做错误的事情。更糟的是,如果你用了 马上 不是描述符(如 子进程.popen 类,在示例中)它现在可以工作,但如果所讨论的对象在将来更改为正常函数,它将混乱地中断。

    最后,只有简单的回调;如果您只想将类的特定实例绑定到特定的服务,可以将服务(或服务的一个或多个方法)传递给类初始值设定项,并让它使用:

    class MyClass(object):
        def __init__(self, authenticate=None, authorize=None):
            if authenticate is None:
                authenticate = default_authenticate
            if authorize is None:
                authorize = default_authorize
            self.authenticate = authenticate
            self.authorize = authorize
        def request(self, user, password, action):
            self.authenticate(user, password)
            self.authorize(user, action)
            self._do_request(action)
    
    ...
    helper = AuthService(...)
    # Pass bound methods to helper.authenticate and helper.authorize to MyClass.
    inst = MyClass(authenticate=helper.authenticate, authorize=helper.authorize)
    inst.request(...)
    

    当设置这样的实例属性时,您不必担心描述符触发,因此只需分配函数(或类、其他可调用文件或实例)就可以了。

        2
  •  1
  •   ankostis    10 年前

    这个“只设定”的注射配方怎么样? http://code.activestate.com/recipes/413268/

    它是相当的蟒蛇,使用“描述符”协议 __get__() / __set__() ,但相当具有侵入性,需要用 RequiredFeature 使用的str名称初始化的实例 Feature 必修的。

        3
  •  0
  •   Sune Andreas Dybro Debel    7 年前

    我最近发布了一个针对Python的DI框架,它可能会对您有所帮助。我认为这是一个相当新鲜的看法,但我不确定它是如何“蟒蛇”。自己判断。欢迎反馈。

    https://github.com/suned/serum

        4
  •  0
  •   Rodrigo Oliveira    7 年前

    @托马斯·沃特斯的回答是完整的。另外:通常我更喜欢最简单的方法,那些不涉及复杂框架和冗长设置的方法。所以我使用最多的是将依赖项作为构造函数参数。

    问题在于,我添加到代码中的样板文件只是为了确保初始化每个依赖项。

    作为装饰师的狂热粉丝,为了从函数的作用域中删除样板代码,我做了一个装饰师来处理:

    @autowired 一个python 3装饰器,用于实现简单、干净的依赖项注入:

    • 该功能完全不需要知道自动布线。
    • 依赖项可以延迟初始化
    • 如果需要,调用方可以显式传递依赖项实例

    装饰师的重点是 这样的代码 :

    def __init__(self, *, model: Model = None, service: Service = None):
        if model is None:
            model = Model()
    
        if service is None:
            service = Service()
    
        self.model = model
        self.service = service
        # actual code
    

    在这个问题上 :

    @autowired
    def __init__(self, *, model: Model, service: Service):
        self.model = model
        self.service = service
        # actual code
    

    没有复杂的东西,没有设置,没有强制执行的工作流。现在您的函数代码不再与依赖项初始化代码混在一起了。

    装饰方法是非常简单的,但它可能是一个完整的框架更适合你的情况。因为有很多优秀的模块,比如 Injector .

    推荐文章