依赖注入的Pythonic方法是什么? 介绍

依赖注入的Pythonic方法是什么? 介绍,python,oop,authentication,dependency-injection,frameworks,Python,Oop,Authentication,Dependency Injection,Frameworks,对于Java,依赖项注入与纯OOP一样工作,即提供要实现的接口,并在框架代码中接受实现已定义接口的类的实例 现在对于Python,您也可以用同样的方法,但是我认为对于Python来说,这种方法的开销太大了。那么,您将如何以Pythonic的方式实现它呢 用例 假设这是框架代码: class FrameworkClass(): def __init__(self, ...): ... def do_the_job(self, ...): # som

对于Java,依赖项注入与纯OOP一样工作,即提供要实现的接口,并在框架代码中接受实现已定义接口的类的实例

现在对于Python,您也可以用同样的方法,但是我认为对于Python来说,这种方法的开销太大了。那么,您将如何以Pythonic的方式实现它呢

用例 假设这是框架代码:

class FrameworkClass():
    def __init__(self, ...):
        ...

    def do_the_job(self, ...):
        # some stuff
        # depending on some external function
基本方法 最简单的(也许是最好的)方法是要求将外部函数提供给
FrameworkClass
构造函数,然后从
do\u作业
方法调用

框架代码:

客户端代码:

问题: 问题很简短。有没有更好的常用的肾盂方法?或者任何支持这种功能的库

最新情况:具体情况 假设我开发了一个microweb框架,它使用令牌处理身份验证。该框架需要一个函数来提供从令牌获得的一些
ID
,并获得与该
ID
对应的用户


显然,框架对用户或任何其他特定于应用程序的逻辑一无所知,因此客户端代码必须将用户getter功能注入框架中,以使身份验证工作正常。

我们在项目中进行依赖项注入的方法是使用lib。看看这本书。我强烈建议将其用于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()
我担心这是python所能做到的(这个模块有一些python的优点,比如通过参数注入decorators等等,请查看文档),因为python没有像接口或类型暗示这样的奇特功能

所以直接回答你的问题将非常困难。我认为真正的问题是:python对DI有一些本机支持吗?遗憾的是,答案是:没有。

有关如何使用超级和多重继承而不是DI的争论,请参见。如果你没有时间看整个视频,跳到第15分钟(但我建议你全部看)

下面是一个如何将本视频中描述的内容应用于您的示例的示例:

框架代码:

客户端代码:

这将起作用,因为Python MRO将保证调用getUserFromToken客户机方法(如果使用super())。如果您使用的是Python2.x,则必须更改代码

这里的另一个好处是,如果客户端不提供实现,这将引发异常


当然,这不是真正的依赖注入,而是多重继承和混合,但它是一种解决问题的Pythonic方法。

我认为DI和AOP通常不被认为是Pythonic的,因为典型的Python开发人员偏好,而不是语言特性


事实上,您可以在中实现一个基本的DI框架。不久前,我写了一个依赖注入微框架,其目标是使其成为Pythonic-。这就是您的代码在使用时的外观:

"""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)
下面是指向此示例更广泛描述的链接-

希望能对你有所帮助。有关更多信息,请访问:

  • GitHub
  • 文件

由于Python OOP实现,IoC和依赖项注入不是Python世界的标准实践。但这种方法甚至对Python来说也是有希望的

  • 使用依赖项作为参数是一种非python方法。Python是一种OOP语言,具有漂亮而优雅的OOP模型,它提供了更直接的方法来维护依赖关系
  • 为了模仿接口类型而定义充满抽象方法的类也很奇怪
  • 巨大的包装上包装解决方案会产生代码开销
  • 当我只需要一个小的模式时,我也不喜欢使用库
因此,我的建议是:

编辑:
注意图案。我在一个真实的项目中使用了它,它显示出了一种不太好的方式

还有Pinject,Google开发的开源python依赖注入程序

这里有一个例子

>>> 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

而且,一种非常简单的python式依赖注入方法importlib

您可以定义一个小的实用函数

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正在使用这样一种模式来定义其数据库连接。

依赖项注入是Python直接支持的一种简单技术。不需要额外的库。使用可提高清晰度和可读性

框架代码:
class UserStore():
"""
用于访问用户信息的基类。
客户端必须扩展此类并实现其方法。
"""
def get_名称(自身、令牌):
引发未实现的错误
类WebFramework():
定义初始化(self,用户存储:用户存储):
self.user\u store=用户\u store
def greet_用户(自身、令牌):
user\u name=self.user\u store.get\u name(令牌)
打印(f'Good day to you,{user_name}!')
客户端代码:
class AlwaysMaryUser(用户存储):
def get_名称(自身、令牌):
返回“玛丽”
类SQLUserStore(UserStore):
定义初始化(self,db_参数):
self.db_params=db_p
# 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         
>>> 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
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")
def myfunction(s):
    print("myfunction in mypackage.mymodule called with parameter:", s)