首页 文章

什么是Pythonic方式进行依赖注入?

提问于
浏览
53

简介

对于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 方法调用 .

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(...)

问题

问题很简短 . 有没有更好的常用Pythonic方法来做到这一点?或者也许任何支持此类功能的库?

更新:具体情况

想象一下,我开发了一个微型Web框架,它使用令牌处理身份验证 . 该框架需要一个函数来提供从令牌获得的一些 ID 并获取与该 ID 对应的用户 .

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

5 回答

  • 1

    有关如何使用超级和多重继承而不是DI的参数,请参阅Raymond Hettinger - Super considered super! - PyCon 2015 . 如果你不推荐观看全部内容) .

    以下是如何将此视频中描述的内容应用于您的示例的示例:

    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(...)
    

    这将起作用,因为Python MRO将保证调用getUserFromToken客户端方法(如果使用了super()) . 如果您使用的是Python 2.x,代码将不得不更改 .

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

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

  • 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没有像接口或类型提示这样的花哨的东西 .

    所以到 answer your question 直接会很难 . 我认为真正的问题是:python是否对DI有一些原生支持?答案是,遗憾的是:不 .

  • 5

    前段时间我写了一个依赖注入微框架,其目标是使它成为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

    希望它可以帮助一点 . 欲了解更多信息,请访问:

  • 42

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

    事实上,您可以使用元类和类装饰器实现a basic DI framework in <100 lines .

    对于侵入性较小的解决方案,这些构造可用于将自定义实现插入到通用框架中 .

  • 0

    由于Python OOP的实现,IoC和依赖注入不是Python世界中的常见做法 . 然而,即使对于Python,这种方法看起

    • 使用依赖关系作为参数,即使它是在同一代码库中定义的类,也是非pythonic方法 . Python是OOP语言,拥有漂亮优雅的OOP模型,所以忽略它并不是一个好主意 .

    • 定义充满抽象方法的类只是为了模仿接口类型也很奇怪 .

    • 巨大的包装程序包装解决方案太优雅,无法使用 .

    • 当我需要的只是一个小模式时,我也不喜欢使用库 .

    所以我的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
    

相关问题