Lazy Callables的Python依赖注入

时间:2016-08-07 19:50:42

标签: python dependency-injection lazy-evaluation

在编程中为了好玩,我注意到管理依赖感觉就像是一件无聊的苦差事,我想尽量减少。 After reading this,我提出了一个超级简单的依赖注入器,通过字符串键查找依赖项实例:

def run_job(job, args, instance_keys, injected):
    args.extend([injected[key] for key in instance_keys])
    return job(*args)

这个便宜的技巧有效,因为我的程序中的调用总是在迭代器中被懒惰地定义(函数句柄与其参数分开存储),例如:

jobs_to_run = [[some_func, ("arg1", "arg2"), ("obj_key",)], [other_func,(),()]]

原因是因为中心main loop必须安排所有事件。它引用了所有依赖项,因此"obj_key"的注入可以在dict对象中传递,例如:

# inside main loop
injection = {"obj_key" : injected_instance}
for (callable, with_args, and_dependencies) in jobs_to_run: 
    run_job(callable, with_args, and_dependencies, injection)

因此,当事件发生时(用户输入等),主循环可能会调用对该输入作出反应的特定对象的update(),而该输入又为{{1}构建作业列表安排什么时候有资源。对我来说,键引用 其他人的所有依赖关系更为清晰,而不是让所有对象形成直接关系。

因为我懒惰地为game clock engine to run them on its own accord定义了所有的callables(函数),所以上述天真的方法很少增加复杂性。但是,必须通过字符串引用对象存在代码。与此同时,传递依赖关系是很臭的,constructor or setter injection会有点矫枉过正,就像最大的dependency injection libraries一样。

对于 callables 中注入依赖项的特殊情况, lazily 已定义,是否有更具表现力的设计存在的模式?

2 个答案:

答案 0 :(得分:1)

  

我注意到管理依赖关系感觉就像一件无聊的苦差事,我想尽量减少。

首先,您不应该假设依赖注入是最大限度地减少依赖管理的工作。它不会消失,只是推迟到另一个地方和时间,并可能委托给其他人。

也就是说,如果您正在构建的内容将被其他人使用,那么将某种形式的版本检查包含在您的“注射器”中是明智的,因为您的用户将有一种简单的方法来检查他们的版本是否匹配预期的那个。

  

是否存在更具表现力的设计模式?

我理解它的方法基本上是Strategy-Pattern,即作业的代码( callable )依赖于在几个具体对象之一上调用方法。你这样做的方式是完全合理的 - 它有效且有效。

您可能希望将其更正式化,以便于阅读和维护,例如

from collections import namedtuple

Job = namedtuple('Job', ['callable', 'args', 'strategies'])

def run_job(job, using=None):
    strategies = { k: using[k] for k in job.strategies] }
    return job.callable(*args, **strategies)

jobs_to_run = [
  Job(callable=some_func, args=(1,2), strategies=('A', 'B')),
  Job(callable=other_func, ...),
]

strategies = {"A": injected_strategy, ...}
for job in jobs_to_run: 
   run_job(job, using=strategies)

# actual job
def some_func(arg1, arg2, A=None, B=None):
   ...

正如您所看到的,代码仍然执行相同的操作,但它立即更具可读性,并且它集中了run_job中有关Job()对象结构的知识。如果传递了错误数量的参数,那么对some_func这样的作业函数的调用将会失败,并且由于其明确列出和命名的参数,作业函数更容易编码和调试。

答案 1 :(得分:0)

如果你不需要总是注入相同的对象,也就是你每次调用注入函数时都可以创建一个新的依赖实例,那么你可能会发现@autowired decorator很有用,它支持懒惰依赖初始化毫不费力。

就像装饰你的功能一样简单。

您可以像这样转换代码:

class Example:
    def __init__(self, *, lazy_service: Service = None):
        self._service = lazy_service

    @property
    def service(self) -> Service:
        if self._service is None:
            self._service = Service()

        return self._service

    # actual code

进入这个:

class Example:
    @autowired(lazy=True)
    def __init__(self, *, lazy_service: Service):
        self.service = service

    # actual code

我维护这个项目。我创建了装饰器来自动化许多不必要的和重复的代码,用于在函数调用者自己不提供依赖项时初始化依赖项实例。