An example of a minimal dependency injection ( aka Inversion of Control ) container for Python.
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: