Python依赖注入,用于惰性调用函数

2024-05-29 10:20:54 发布

您现在位置:Python中文网/ 问答频道 /正文

在为好玩而编程时,我注意到管理依赖项感觉像是一项无聊的杂务,我想尽量减少。After reading this,我想出了一个非常简单的依赖注入器,通过这个方法,依赖实例通过一个字符串键来查找:

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

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

^{pr2}$

原因是因为中央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)

{{3}在一个特定的输入事件中,{etc}调用一个特定的输入时,{3}用户可以调用一个cd3}的输入。对我来说,对于键引用其他人的任何依赖关系都可以注入,而不是让所有对象形成直接关系。

因为我懒洋洋地定义了game clock engine to run them on its own accord的所有可调用(函数),所以上述天真的方法只增加了很少的复杂性。不过,必须通过字符串引用对象还是有代码问题的。同时,传递依赖项是很糟糕的,而且constructor or setter injection可能是过度杀戮,也许大多数大型的dependency injection libraries也是如此。

对于在可调用中注入依赖的特殊情况,惰性地定义,是否存在更具表现力的设计模式?


Tags: 对象方法instancekey函数run字符串in
2条回答

如果您不需要总是注入同一个对象,也就是说,您可以在每次调用注入的函数时创建一个新的依赖实例,那么您可能会发现^{} 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

在这方面:

^{pr2}$

我维护这个项目。我创建了decorator来自动化许多不必要的重复代码,这些代码在函数调用程序本身不提供依赖项时用于初始化依赖项实例。在

I've noticed that managing dependencies feels like a boring chore that I want to minimize.

首先,您不应该假设依赖注入是一种最小化依赖管理繁琐工作的方法。它不会消失,它只是被推迟到另一个地方和时间,并可能委托给其他人。在

这就是说,如果您正在构建的内容将被其他人使用,那么明智的做法是在您的“可注入项”中包含某种形式的版本检查,这样您的用户就可以轻松地检查他们的版本是否与预期的版本相匹配。在

are there more expressive design patterns in existence?

实际上,调用一个具体的方法依赖于一个具体的方法。你做这件事的方式是完全合理的-它是有效的。在

你可能想把它正式化一点,以便于阅读和维护

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之类的作业函数的调用将失败,而且由于作业函数的参数是明确列出和命名的,因此它们更易于编码和调试。在

相关问题 更多 >

    热门问题