What is a Pythonic way for Dependency Injection?

后端 未结 9 2053
终归单人心
终归单人心 2020-12-02 06:57

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 c

相关标签:
9条回答
  • 2020-12-02 07:42

    Dependency injection is a simple technique that Python supports directly. No additional libraries are required. Using type hints can improve clarity and readability.

    Framework Code:

    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}!')
    

    Client Code:

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

    The UserStore class and type hinting are not required for implementing dependency injection. Their primary purpose is to provide guidance to the client developer. If you remove the UserStore class and all references to it, the code still works.

    0 讨论(0)
  • 2020-12-02 07:43

    The way we do dependency injection in our project is by using the inject lib. 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.

    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.

    So to answer your question directly would be very hard. I think the true question is: does python have some native support for DI? And the answer is, sadly: no.

    0 讨论(0)
  • 2020-12-02 07:44

    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.

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

    So my solution is:

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

    0 讨论(0)
  • 2020-12-02 07:48

    See Raymond Hettinger - Super considered super! - PyCon 2015 for 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).

    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.

    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.

    0 讨论(0)
  • 2020-12-02 07:48

    After playing around with some of the DI frameworks in python, I've found they have felt a bit clunky to use when comparing how simple it is in other realms such as with .NET Core. This is mostly due to the joining via things like decorators that clutter the code and make it hard to simply add it into or remove it from a project, or joining based on variable names.

    I've recently been working on a dependency injection framework that instead uses typing annotations to do the injection called Simple-Injection. Below is a simple example

    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!
    

    This library supports service lifetimes and binding services to implementations.

    One of the goals of this library is that it is also easy to add it to an existing application and see how you like it before committing to it as all it requires is your application to have appropriate typings, and then you build the dependency graph at the entry point and run it.

    Hope this helps. For more information, please see

    • github: https://github.com/BradLewis/simple-injection
    • docs: https://simple-injection.readthedocs.io/en/latest/
    • pypi: https://pypi.org/project/simple-injection/
    0 讨论(0)
  • 2020-12-02 07:49

    There is also Pinject, an open source python dependency injector by Google.

    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
    

    And here is the source code

    0 讨论(0)
提交回复
热议问题