:( Somehow this recipe arived twice. Please go to http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/413268 :o Dear editors, please remove this one
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | ######################################################################
##
## Feature Broker
##
######################################################################
class FeatureBroker:
def __init__(self, allowReplace=False):
self.providers = {}
self.allowReplace = allowReplace
def Provide(self, feature, provider, *args, **kwargs):
if not self.allowReplace:
assert not self.providers.has_key(feature), "Duplicate feature: %r" % feature
if callable(provider):
def call(): return provider(*args, **kwargs)
else:
def call(): return provider
self.providers[feature] = call
def __getitem__(self, feature):
try:
provider = self.providers[feature]
except KeyError:
raise KeyError, "Unknown feature named %r" % feature
return provider()
features = FeatureBroker()
######################################################################
##
## Representation of Required Features and Feature Assertions
##
######################################################################
#
# Some basic assertions to test the suitability of injected features
#
def NoAssertion(obj): return True
def IsInstanceOf(*classes):
def test(obj): return isinstance(obj, classes)
return test
def HasAttributes(*attributes):
def test(obj):
for each in attributes:
if not hasattr(obj, each): return False
return True
return test
def HasMethods(*methods):
def test(obj):
for each in methods:
try:
attr = getattr(obj, each)
except AttributeError:
return False
if not callable(attr): return False
return True
return test
#
# An attribute descriptor to "declare" required features
#
class RequiredFeature(object):
def __init__(self, feature, assertion=NoAssertion):
self.feature = feature
self.assertion = assertion
def __get__(self, obj, T):
return self.result # <-- will request the feature upon first call
def __getattr__(self, name):
assert name == 'result', "Unexpected attribute request other then 'result'"
self.result = self.Request()
return self.result
def Request(self):
obj = features[self.feature]
assert self.assertion(obj), \
"The value %r of %r does not match the specified criteria" \
% (obj, self.feature)
return obj
class Component(object):
"Symbolic base class for components"
######################################################################
##
## DEMO
##
######################################################################
# ---------------------------------------------------------------------------------
# Some python module defines a Bar component and states the dependencies
# We will assume that
# - Console denotes an object with a method WriteLine(string)
# - AppTitle denotes a string that represents the current application name
# - CurrentUser denotes a string that represents the current user name
#
class Bar(Component):
con = RequiredFeature('Console', HasMethods('WriteLine'))
title = RequiredFeature('AppTitle', IsInstanceOf(str))
user = RequiredFeature('CurrentUser', IsInstanceOf(str))
def __init__(self):
self.X = 0
def PrintYourself(self):
self.con.WriteLine('-- Bar instance --')
self.con.WriteLine('Title: %s' % self.title)
self.con.WriteLine('User: %s' % self.user)
self.con.WriteLine('X: %d' % self.X)
# ---------------------------------------------------------------------------------
# Some other python module defines a basic Console component
#
class SimpleConsole(Component):
def WriteLine(self, s):
print s
# ---------------------------------------------------------------------------------
# Yet another python module defines a better Console component
#
class BetterConsole(Component):
def __init__(self, prefix=''):
self.prefix = prefix
def WriteLine(self, s):
lines = s.split('\n')
for line in lines:
if line:
print self.prefix, line
else:
print
# ---------------------------------------------------------------------------------
# Some third python module knows how to discover the current user's name
#
def GetCurrentUser():
return 'Some User'
# ---------------------------------------------------------------------------------
# Finally, the main python script specifies the application name,
# decides which components/values to use for what feature,
# and creates an instance of Bar to work with
#
if __name__ == '__main__':
print '\n*** IoC Demo ***'
features.Provide('AppTitle', 'Inversion of Control ...\n\n... The Python Way')
features.Provide('CurrentUser', GetCurrentUser)
features.Provide('Console', BetterConsole, prefix='-->') # <-- transient lifestyle
##features.Provide('Console', BetterConsole(prefix='-->')) # <-- singleton lifestyle
bar = Bar()
bar.PrintYourself()
#
# Evidently, none of the used components needed to know about each other
# => Loose coupling goal achieved
# ---------------------------------------------------------------------------------
|
Inversion of Control (IoC) Containers and the Dependency Injection pattern have drawn some attention in the Java world, and they are increasingly spreading over to .NET, too. (Perhaps we are facing a sort of "Infection OUT of Control" - IooC? ;)
IoC is all about loose coupling between components of an application, about cutting off explicit, direct dependencies, plus some goodies (most of which are useful in statically languages only, like automatic type/interface matching). A thorough discussion on the subject can be found at http://www.martinfowler.com/articles/injection.html .
In statically typed languages, an IoC container is quite a big deal. But fact is that, in the end, there are only few key concepts behind it.
- Dependencies are not specified directly, but using a sort of a key.
- Dependencies are resolved late, preferably just before they are used (JIT dependency resolution).
- Dependencies are resolved once for each component.
Wait - I though - it should not be such a big deal to do this in python!
And indeed, a combination of a broker, descriptors and lazy attributes brings about pretty much the same core result as those IoC containers - effectively in much less then 100 lines.
(I suspect I am starting to find a bizarre joy in pointing out how things get done in python with a fraction of the code amount needed elsewhere ;)
Cheers and happy dependency inverting!
[See Also]
Recipe "Loose Coupling" by Jimmy Retzlaff for a basic broker implementation at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81983
Recipe "lazy attributes" by Sébastien Keim at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/131495
Go to "My Recipes" On the left side of the page is
Submit Recipe My Recipe
All Recipes All Cookbooks
...
Clink on "My Recipes" to get rid of this duplicate.
... and then? I know where to find "My Recipes" (but thanks anyway). What I don't know is how to delete a recipe when I'm there. I only see "Edit | View", no "Delete" or the like. Am I constantly overlooking something?