Welcome, guest | Sign In | My Account | Store | Cart

An example of a minimal dependency injection ( aka Inversion of Control ) container for Python.

Python, 89 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from inspect import getmembers, ismethod

_NOVALUE = object()
class _Memoized(object):
    def __init__(self):
       self.value = _NOVALUE 

    def __nonzero__(self):
        return (self.value is not _NOVALUE)

class singleton(object):
    def __init__(self, func):
        self.func = func

    def __call__(self):
        memoized = _Memoized()

        def singleton_wrapper(instance_self, *args, **kwargs):
            if args or kwargs:
                raise TypeError, ("Singleton-wrapped methods shouldn't take"
                        "any argument! (%s)" % self.func)                            
            if not memoized:
                memoized.value = self.func(instance_self)
            return memoized.value

        return singleton_wrapper

class prototype(object):
    def __init__(self, func):
        self.func = func

class _Container(object):
    def __call__(self, klass):
        subklass_dict = dict(klass.__dict__)
        self._set_singletons(subklass_dict, klass)
        self._set_prototypes(subklass_dict, klass)
        return type(klass.__name__, (klass, ), subklass_dict)

    def _set_singletons(self, subklass_dict, klass):
        for name, singletoned in ((n, f) for (n, f) in getmembers(klass, 
            lambda x: isinstance(x, singleton)) ):
            subklass_dict[name] = singletoned()

    def _set_prototypes(self, subklass_dict, klass):
        for name, prototyped in ((n, f) for (n, f) in getmembers(klass, 
            lambda x: isinstance(x, prototype))):
            subklass_dict[name] = prototyped.func

Container = _Container()

if __name__ == "__main__":

    class MySomething(object):
        pass

    class TheObject(object):
        def __init__(self, someattr):
            self.someattr = someattr

    class MyContainer(object):
        def __init__(self, config):
            self.config = config

        @singleton
        def theobject(self):
            return TheObject(self.config["someattr"])

        @prototype
        def something(self, value):
            o = MySomething()
            o.value = value
            o.obj = self.theobject()
            return o

    # python >= 2.6 users can use class decorators.
    MyContainer = Container(MyContainer)

    mc = MyContainer({"someattr": "a"})
    something1 = mc.something(1)
    something2 = mc.something(2)
    theobject = mc.theobject()
    
    assert theobject.someattr == "a"
    assert something1.value == 1
    assert something2.value == 2
    assert (something1.obj is something2.obj)
    assert (something2.obj is theobject) 

Minimal dependency injection container for Python, allows quick configuration of your python code in a declarative style; you needn't deciding which object to instantiate first, everything is lazily instantiated at runtime. If you need to pass a factory somewhere, you can just pass the bound method around.

Other solutions exist for Python; another ASPN recipe which I feel too obtrusive (ties your app to its implementation), then there's spring-python, which is way too big and tries to do too many things, snake-guice, which uses decorators and has similar issues to the ASPN recipe, and pinsor, which is a bit too verbose and has been discontinued.

I think this is a very concise and clean way to apply dependency injection in python. You needn't modifying any of your existing application code, it's just your main application-launcher module that can employ this recipe to configure & launch other classes.

I'm also planning a somewhat more complete DI container for Python which can be truly pythonic and offer all and only the features that can apply to python programming; check pydenji development.

Limitations:

  • no way of merging multiple configurations
  • doesn't detect configuration loops
  • has no builtin way of reading config options from a config file of some sort
  • Container-decorated classes mustn't be re-inherited and re-decorated or issues will arise.

Further references:

Inspiration: