Python Lazy Callables 的依赖注入
Python Dependency Injection for Lazy Callables
在为乐趣而编程的过程中,我注意到管理依赖项感觉就像是一件我想尽量减少的无聊琐事。 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"
的注入可以在字典对象中传递,例如:
# 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()
,后者又会为 main loop
在有资源的时候安排。对我来说,key-reference 任何依赖项 someone else 注入而不是让所有对象形成直接关系。
因为我正在懒惰地为 game clock engine to run them on its own accord, the above naive approach worked with very little added complexity. Still, there is a code stink in having to reference objects by strings. At the same time, it was stinky to be passing dependencies around, and constructor or setter injection would be overkill, as would perhaps most large dependency injection libraries.
定义所有可调用对象(函数)
对于在 callables 中注入依赖项的特殊情况 lazily 定义了,还有没有表现力更强的设计模式?
I've noticed that managing dependencies feels like a boring chore that I want to minimize.
首先,您不应该假设依赖注入是一种最小化依赖管理工作的方法。它不会消失,只是被推迟到另一个地方和时间,并可能委托给其他人。
就是说,如果您正在构建的内容将被其他人使用,那么明智的做法是将某种形式的版本检查包含到您的“可注入”中,以便您的用户可以轻松地检查他们是否版本与预期版本匹配。
are there more expressive design patterns in existence?
据我了解,您的方法本质上是一个 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
之类的作业函数的调用将失败,并且作业函数由于其明确列出和命名的参数而更易于编码和调试。
关于字符串,您可以在 dependencies.py
文件中将它们设为常量并使用这些常量。
一个开销仍然很小的更健壮的选择是使用依赖注入框架,例如 Injectable:
@autowired
def job42(some_instance: Autowired("SomeInstance", lazy=true)):
...
# some_instance is autowired to job42 calls and
# it will be automatically injected for you
job42()
披露:我是项目维护者。
在为乐趣而编程的过程中,我注意到管理依赖项感觉就像是一件我想尽量减少的无聊琐事。 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"
的注入可以在字典对象中传递,例如:
# 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()
,后者又会为 main loop
在有资源的时候安排。对我来说,key-reference 任何依赖项 someone else 注入而不是让所有对象形成直接关系。
因为我正在懒惰地为 game clock engine to run them on its own accord, the above naive approach worked with very little added complexity. Still, there is a code stink in having to reference objects by strings. At the same time, it was stinky to be passing dependencies around, and constructor or setter injection would be overkill, as would perhaps most large dependency injection libraries.
定义所有可调用对象(函数)对于在 callables 中注入依赖项的特殊情况 lazily 定义了,还有没有表现力更强的设计模式?
I've noticed that managing dependencies feels like a boring chore that I want to minimize.
首先,您不应该假设依赖注入是一种最小化依赖管理工作的方法。它不会消失,只是被推迟到另一个地方和时间,并可能委托给其他人。
就是说,如果您正在构建的内容将被其他人使用,那么明智的做法是将某种形式的版本检查包含到您的“可注入”中,以便您的用户可以轻松地检查他们是否版本与预期版本匹配。
are there more expressive design patterns in existence?
据我了解,您的方法本质上是一个 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
之类的作业函数的调用将失败,并且作业函数由于其明确列出和命名的参数而更易于编码和调试。
关于字符串,您可以在 dependencies.py
文件中将它们设为常量并使用这些常量。
一个开销仍然很小的更健壮的选择是使用依赖注入框架,例如 Injectable:
@autowired
def job42(some_instance: Autowired("SomeInstance", lazy=true)):
...
# some_instance is autowired to job42 calls and
# it will be automatically injected for you
job42()
披露:我是项目维护者。