什么是依赖注入的 Pythonic 方式?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31678827/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is a Pythonic way for Dependency Injection?
提问by bagrat
Introduction
介绍
For Java, Dependency Injection works as pure OOP, i.e. you provide an interface to be implemented and in your framework code accept an instance of a class that implements the defined interface.
对于 Java,依赖注入作为纯 OOP 工作,即您提供一个要实现的接口,并在您的框架代码中接受一个实现定义接口的类的实例。
Now for Python, you are able to do the same way, but I think that method was too much overhead right in case of Python. So then how would you implement it in the Pythonic way?
现在对于 Python,您可以使用相同的方法,但我认为这种方法对于 Python 来说开销太大了。那么你将如何以 Pythonic 的方式实现它呢?
Use Case
用例
Say this is the framework code:
说这是框架代码:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
The Basic Approach
基本方法
The most naive (and maybe the best?) way is to require the external function to be supplied into the FrameworkClass
constructor, and then be invoked from the do_the_job
method.
最天真的(也许是最好的?)方法是要求将外部函数提供给FrameworkClass
构造函数,然后从do_the_job
方法中调用。
Framework Code:
框架代码:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Client Code:
客户代码:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Question
题
The question is short. Is there any better commonly used Pythonic way to do this? Or maybe any libraries supporting such functionality?
问题很短。有没有更好的常用Pythonic方法来做到这一点?或者可能有任何支持此类功能的库?
UPDATE: Concrete Situation
更新:具体情况
Imagine I develop a micro web framework, which handles authentication using tokens. This framework needs a function to supply some ID
obtained from the token and get the user corresponding to that ID
.
想象一下,我开发了一个微型 Web 框架,它使用令牌处理身份验证。这个框架需要一个函数来提供一些ID
从令牌中获得的信息,并获取与该令牌相对应的用户ID
。
Obviously, the framework does not know anything about users or any other application specific logic, so the client code must inject the user getter functionality into the framework to make the authentication work.
显然,框架对用户或任何其他特定于应用程序的逻辑一无所知,因此客户端代码必须将用户 getter 功能注入框架以进行身份验证。
采纳答案by Serban Teodorescu
See Raymond Hettinger - Super considered super! - PyCon 2015for an argument about how to use super and multiple inheritance instead of DI. If you don't have time to watch the whole video, jump to minute 15 (but I'd recommend watching all of it).
参见Raymond Hettinger - 超级被认为是超级!- PyCon 2015关于如何使用超级继承和多重继承而不是 DI 的争论。如果您没有时间观看整个视频,请跳至第 15 分钟(但我建议您观看所有视频)。
Here is an example of how to apply what's described in this video to your example:
以下是如何将本视频中描述的内容应用于您的示例的示例:
Framework Code:
框架代码:
class TokenInterface():
def getUserFromToken(self, token):
raise NotImplementedError
class FrameworkClass(TokenInterface):
def do_the_job(self, ...):
# some stuff
self.user = super().getUserFromToken(...)
Client Code:
客户代码:
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(...)
This will work because the Python MRO will guarantee that the getUserFromToken client method is called (if super() is used). The code will have to change if you're on Python 2.x.
这将起作用,因为 Python MRO 将保证调用 getUserFromToken 客户端方法(如果使用 super())。如果您使用的是 Python 2.x,则必须更改代码。
One added benefit here is that this will raise an exception if the client does not provide a implementation.
这里的另一个好处是,如果客户端不提供实现,这将引发异常。
Of course, this is not really dependency injection, it's multiple inheritance and mixins, but it is a Pythonic way to solve your problem.
当然,这并不是真正的依赖注入,它是多重继承和混合,但它是解决问题的 Pythonic 方式。
回答by Piotr Mazurek
The way we do dependency injection in our project is by using the injectlib. Check out the documentation. I highly recommend using it for DI. It kinda makes no sense with just one function but starts making lots of sense when you have to manage multiple data sources etc, etc.
我们在项目中进行依赖注入的方式是使用注入库。查看文档。我强烈建议将它用于 DI。仅使用一个功能有点没有意义,但是当您必须管理多个数据源等时开始变得很有意义。
Following your example it could be something similar to:
按照您的示例,它可能类似于:
# framework.py
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self):
# some stuff
self.func()
Your custom function:
您的自定义功能:
# my_stuff.py
def my_func():
print('aww yiss')
Somewhere in the application you want to create a bootstrap file that keeps track of all the defined dependencies:
在应用程序中的某个地方,您希望创建一个引导程序文件来跟踪所有定义的依赖项:
# bootstrap.py
import inject
from .my_stuff import my_func
def configure_injection(binder):
binder.bind(FrameworkClass, FrameworkClass(my_func))
inject.configure(configure_injection)
And then you could consume the code this way:
然后你可以这样使用代码:
# 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()
I'm afraid this is as pythonic as it can get (the module has some python sweetness like decorators to inject by parameter etc - check the docs), as python does not have fancy stuff like interfaces or type hinting.
恐怕这就像它所能得到的那样 Pythonic(该模块有一些 Python 甜蜜,比如通过参数注入的装饰器等 - 检查文档),因为 python 没有像接口或类型提示这样的花哨的东西。
So to answer your questiondirectly would be very hard. I think the true question is: does python have some native support for DI? And the answer is, sadly: no.
所以直接回答你的问题会很困难。我认为真正的问题是:python 是否对 DI 有一些本机支持?可悲的是,答案是:不。
回答by Andrea Ratto
I think that DI and possibly AOP are not generally considered Pythonic because of typical Python developers preferences, rather that language features.
我认为 DI 和可能的 AOP 通常不被认为是 Pythonic,因为典型的 Python 开发人员偏好,而是语言特性。
As a matter of fact you can implement a basic DI framework in <100 lines, using metaclasses and class decorators.
事实上,您可以使用元类和类装饰器在 <100 行内实现一个基本的 DI 框架。
For a less invasive solution, these constructs can be used to plug-in custom implementations into a generic framework.
对于侵入性较小的解决方案,这些构造可用于将自定义实现插入通用框架中。
回答by Roman Mogylatov
Some time ago I wrote dependency injection microframework with a ambition to make it Pythonic - Dependency Injector. That's how your code can look like in case of its usage:
前段时间,我写了一个依赖注入微框架,雄心勃勃地使它成为 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)
Here is a link to more extensive description of this example - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
这是此示例更广泛描述的链接 - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
Hope it can help a bit. For more information please visit:
希望它可以帮助一点。欲了解更多信息,请访问:
回答by I159
Due to Python OOP implementation, IoC and dependency injection are not standard practices in the Python world. But the approach seems promising even for Python.
由于 Python OOP 实现,IoC 和依赖注入不是 Python 世界中的标准做法。但即使对于 Python,这种方法似乎也很有希望。
- To use dependencies as arguments is a non-pythonic approach. Python is an OOP language with beautiful and elegant OOP model, that provides more straightforward ways to maintain dependencies.
- To define classes full of abstract methods just to imitate interface type is weird too.
- Huge wrapper-on-wrapper workarounds create code overhead.
- I also don't like to use libraries when all I need is a small pattern.
- 使用依赖项作为参数是一种非 Python 的方法。Python 是一种 OOP 语言,具有漂亮优雅的 OOP 模型,它提供了更直接的方式来维护依赖项。
- 定义充满抽象方法的类只是为了模仿接口类型也很奇怪。
- 巨大的包装器上包装器变通方法会产生代码开销。
- 当我只需要一个小模式时,我也不喜欢使用库。
So my solutionis:
所以我的解决方案是:
# Framework internal
def MetaIoC(name, bases, namespace):
cls = type("IoC{}".format(name), tuple(), namespace)
return type(name, bases + (cls,), {})
# Entities level
class Entity:
def _lower_level_meth(self):
raise NotImplementedError
@property
def entity_prop(self):
return super(Entity, self)._lower_level_meth()
# Adapters level
class ImplementedEntity(Entity, metaclass=MetaIoC):
__private = 'private attribute value'
def __init__(self, pub_attr):
self.pub_attr = pub_attr
def _lower_level_meth(self):
print('{}\n{}'.format(self.pub_attr, self.__private))
# Infrastructure level
if __name__ == '__main__':
ENTITY = ImplementedEntity('public attribute value')
ENTITY.entity_prop
EDIT:
编辑:
Be careful with the pattern. I used it in a real project and it showed itself a not that good way. My post on Medium about my experience with the pattern.
小心图案。我在一个真实的项目中使用了它,它显示了一个不太好的方法。我在 Medium 上发表的关于我使用该模式的经验的帖子。
回答by Nasser Abdou
There is also Pinject, an open source python dependency injector by Google.
还有 Pinject,Google 的开源 Python 依赖注入器。
Here is an example
这是一个例子
>>> class OuterClass(object):
... def __init__(self, inner_class):
... self.inner_class = inner_class
...
>>> class InnerClass(object):
... def __init__(self):
... self.forty_two = 42
...
>>> obj_graph = pinject.new_object_graph()
>>> outer_class = obj_graph.provide(OuterClass)
>>> print outer_class.inner_class.forty_two
42
回答by Ruben Decrop
A very easy and Pythonic way to do dependency injection is importlib.
进行依赖注入的一种非常简单且 Pythonic 的方法是 importlib。
You could define a small utility function
你可以定义一个小的效用函数
def inject_method_from_module(modulename, methodname):
"""
injects dynamically a method in a module
"""
mod = importlib.import_module(modulename)
return getattr(mod, methodname, None)
And then you can use it:
然后你可以使用它:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction")
myfunction("a")
In mypackage/mymodule.py you define myfunction
在 mypackage/mymodule.py 中定义 myfunction
def myfunction(s):
print("myfunction in mypackage.mymodule called with parameter:", s)
You could of course also use a class MyClass iso. the function myfunction. If you define the values of methodname in a settings.py file you can load different versions of the methodname depending on the value of the settings file. Django is using such a scheme to define its database connection.
您当然也可以使用类 MyClass iso。函数 myfunction. 如果在 settings.py 文件中定义 methodname 的值,则可以根据设置文件的值加载不同版本的 methodname。Django 正在使用这样的方案来定义其数据库连接。