对于Java,依赖注入作为纯OOP工作,即您提供要实现的接口,并在您的框架代码中接受实现已定义接口的类的实例。
现在对于Python,您可以采用相同的方式,但我认为在Python的情况下,该方法的开销太大了。那么你将如何以Pythonic的方式实现它呢?
说这是框架代码:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
最天真(也许是最好的?)方式是要求将外部函数提供给FrameworkClass
构造函数,然后从do_the_job
方法调用。
框架代码:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
客户代码:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
问题很简短。有没有更好的常用Pythonic方法来做到这一点?或者也许任何支持此类功能的库?
想象一下,我开发了一个微型Web框架,它使用令牌处理身份验证。此框架需要一个函数来提供从令牌获取的一些ID
,并获取与ID
对应的用户。
显然,框架对用户或任何其他特定于应用程序的逻辑一无所知,因此客户端代码必须将用户getter功能注入框架,以使身份验证工作。
答案 0 :(得分:44)
有关如何使用超级和多重继承而不是DI的参数,请参阅Raymond Hettinger - Super considered super! - PyCon 2015。如果您没有时间观看整个视频,请跳至第15分钟(但我建议您观看所有视频)。
以下是如何将此视频中描述的内容应用于您的示例的示例:
框架代码:
class TokenInterface():
def getUserFromToken(self, token):
raise NotImplementedError
class FrameworkClass(TokenInterface):
def do_the_job(self, ...):
# some stuff
self.user = super().getUserFromToken(...)
客户代码:
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(...)
这将起作用,因为Python MRO将保证调用getUserFromToken客户端方法(如果使用了super())。如果您使用的是Python 2.x,则必须更改代码。
这里的一个额外好处是,如果客户端没有提供实现,这将引发异常。
当然,这不是依赖注入,它是多重继承和mixins,但它是解决问题的Pythonic方法。
答案 1 :(得分:15)
我们在项目中进行依赖注入的方式是使用inject lib。查看documentation。我强烈建议将它用于DI。只需要一个函数就没有任何意义,但是当你必须管理多个数据源等时,它会开始变得很有意义。
按照你的例子,它可能类似于:
# framework.py
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self):
# some stuff
self.func()
您的自定义功能:
# my_stuff.py
def my_func():
print('aww yiss')
在应用程序的某个位置,您要创建一个跟踪所有已定义依赖项的引导程序文件:
# bootstrap.py
import inject
from .my_stuff import my_func
def configure_injection(binder):
binder.bind(FrameworkClass, FrameworkClass(my_func))
inject.configure(configure_injection)
然后你可以这样使用代码:
# 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()
我担心这会像pythonic一样(模块有一些python甜味,就像装饰器通过参数注入等等 - 检查文档),因为python没有像接口或类型提示这样的花哨的东西。
所以直接直接回答你的问题会非常困难。我认为真正的问题是:python是否对DI有一些原生支持?答案是,遗憾的是:不。
答案 2 :(得分:6)
前段时间我编写了依赖注入微框架,其目标是使其成为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)
以下是此示例的更详尽说明的链接 - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
希望它有所帮助。欲了解更多信息,请访问:
答案 3 :(得分:3)
依赖注入是Python直接支持的一种简单技术。不需要其他库。使用this documentation可以提高清晰度和可读性。
class UserStore():
"""
The base class for accessing a user's information.
The client must extend this class and implement its methods.
"""
def get_name(self, token):
raise NotImplementedError
class WebFramework():
def __init__(self, user_store: UserStore):
self.user_store = user_store
def greet_user(self, token):
user_name = self.user_store.get_name(token)
print(f'Good day to you, {user_name}!')
class AlwaysMaryUser(UserStore):
def get_name(self, token):
return 'Mary'
class SQLUserStore(UserStore):
def __init__(self, db_params):
self.db_params = db_params
def get_name(self, token):
# TODO: Implement the database lookup
raise NotImplementedError
client = WebFramework(AlwaysMaryUser())
client.greet_user('user_token')
实现依赖项注入不需要UserStore
类和类型提示。他们的主要目的是为客户开发人员提供指导。如果删除UserStore
类及其所有引用,该代码仍然有效。
答案 4 :(得分:1)
我认为DI和可能的AOP通常不被认为是Pythonic,因为典型的Python开发人员偏好,而不是语言功能。
事实上,您可以使用元类和类装饰器来实现a basic DI framework in <100 lines。
对于侵入性较小的解决方案,这些构造可用于将自定义实现插入到通用框架中。
答案 5 :(得分:1)
importlib是一种非常简单且Pythonic的依赖项注入方法。
您可以定义一个小的实用程序功能
def inject_method_from_module(modulename, methodname):
"""
injects dynamically a method in a module
"""
mod = importlib.import_module(modulename)
return getattr(mod, methodname, None)
然后您可以使用它:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction")
myfunction("a")
在mypackage / mymodule.py中,定义myfunction
def myfunction(s):
print("myfunction in mypackage.mymodule called with parameter:", s)
您当然也可以使用MyClass iso类。函数myfunction。如果您在settings.py文件中定义methodname的值,则可以根据设置文件的值加载不同版本的methodname。 Django使用这种方案来定义其数据库连接。
答案 6 :(得分:1)
在python中使用了一些DI框架之后,我发现在比较其他领域(例如.NET Core)的简单程度时,它们使用起来有些笨拙。这主要是由于通过诸如装饰器之类的连接而使代码变得混乱,使其难以简单地将其添加到项目中或从项目中删除,或者基于变量名进行连接。
我最近一直在研究依赖项注入框架,该框架改为使用键入注释来进行称为Simple-Injection的注入。下面是一个简单的示例
from simple_injection import ServiceCollection
class Dependency:
def hello(self):
print("Hello from Dependency!")
class Service:
def __init__(self, dependency: Dependency):
self._dependency = dependency
def hello(self):
self._dependency.hello()
collection = ServiceCollection()
collection.add_transient(Dependency)
collection.add_transient(Service)
collection.resolve(Service).hello()
# Outputs: Hello from Dependency!
该库支持服务生存期并将服务绑定到实现。
该库的目标之一是,还可以轻松地将其添加到现有应用程序中,并在提交之前查看您的喜好,因为它所需要的只是您的应用程序具有适当的类型,然后您可以构建该库。依赖点在入口点并运行它。
希望这会有所帮助。有关更多信息,请参见
答案 7 :(得分:0)
由于Python OOP的实现,IoC和依赖项注入在Python世界中并不常见。尽管如此,即使对于Python来说,这种方法也很有希望。
所以我的solution是:
# 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
答案 8 :(得分:0)