In recipe http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308 Alex Martelli showed how a collection of stuff can be organized as a "Bunch". This recipe recommends a specialized dictionary to reach the same goal. Since Python 2.2 the new dictionary-class can easily be created by subclassing the built in 'dict' type. Adding a special method to (re)present the proposed BunchDict()-class we arrive at CfgBunch(), which is very well suited to contain bunches of configuration data.
It is further shown how these - in itself - rather unspectacular components gain considerable power when applied in a programming technique where "all sorts of sets of parameters" are stored in separate CfgBunch()-instances. Referencing a parameter according to the proposed technique makes as little difference as writing "self.clevername" versus "self.cfg.clevername". The benefits are: - having as many namespaces as needed to organize parameters - ease of use - possibility to easily pass sets of parameters around - great help in documentation
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 161 162 163 164 165 166 167 168 | # since Python 2.2 we can directly inherit from
# the builtin type/class 'dict'
class BunchDict(dict):
# since Python 2.3 this __new__-method could be omitted
def __new__(cls,**kwds):
return dict.__new__(cls, kwds)
def __getattr__(self, attr):
"Provide attribute access to retrieve dictionary items."
# in case of error AttributeError is to be
# raised instead of KeyError
try:
return self[attr]
except KeyError:
raise AttributeError, ("'%s' instance has no attribute '%s'" %
(self.__class__.__name__, attr))
def __setattr__(self, attr, val):
"Provide attribute access to store dictionary items."
self[attr] = val
class CfgBunch(BunchDict):
"A class able to represent itself as Python source."
def lines(self, _instancename=None, _classname=None, indentwidth=4):
"""Return a Python source representation."""
_instancename = (_instancename or
self.get("_instancename",
"a%s" % self.__class__.__name__))
_classname = (_classname or
self.get("_classname", None) or
self.__class__.__name__)
keys = self.keys()
keys.sort()
result = []
result.append(("%s = %s(\n" ) % (_instancename, _classname))
for k in keys:
result.append( "%*s%s = %r,\n" % (indentwidth,' ',k, self[k]))
result.append("%*s)" % (indentwidth,' '))
return result
def __str__(self):
return "".join(self.lines())
def __repr__(self):
return "".join(self.lines())
if __name__=="__main__":
if 1 and "example use of CfgBunch()":
# handy initialization via keyword parameters
cfg = CfgBunch(host="localhost", db="test",
user="test", passwd="test")
# handy item access
host = cfg.host
# is otherwiese dictionary-like
if cfg['host'] == cfg.user:
"Hui?"
# raise "KeyError: tralala"
try:
cfg['tralala']
except KeyError:
pass
# raise "AttributeError: 'CfgBunch' instance has no attribute 'tralala'"
try:
cfg.tralala
except AttributeError:
pass
# store new item
cfg.one = 1
# representation as well readable Python source
print cfg
#>>> print cfg
#aCfgBunch = CfgBunch(
# db = 'test',
# host = 'localhost',
# one = 1,
# passwd = 'test',
# user = 'test',
# )
if __name__=="__main__":
if 1 and "how to organize your main program":
class BigWorkerClass(object):
"""
This class stands as an example for a big
independent class. It receives a reference
to the 'Main' instance providing access to
all its attributes. Notice the readability:
its easy to recognize that 'self.main.cfg.talk'
refers to the same parameter as 'self.cfg.talk'
in the 'main'-instance.
"""
def __init__(self,main):
self.main = main
if self.main.cfg.talk:
"Yeah!"
class Main(object):
def __init__(self,**kwds):
defaults = {"talk":0}
self.cfg = CfgBunch(**defaults)
self.cfg.update(kwds)
self.cfg._instancename = 'Main.cfg'
self.cfg_connection = CfgBunch(host="localhost",user="test",
_instancename="Main.cfg_connection")
# pass self.cfg around
# even more complete: we pass 'self' directly
self.bwc = BigWorkerClass(self) # notice the (self)!
def work(self):
# use parameters
if self.cfg.talk:
"talk!"
# access the same parameter, demonstrating readability
# consistent naming counts!
if self.bwc.main.cfg.talk:
"as I said"
def explain(self):
print "#"
print "# configuration:"
print "#"
print self.cfg
print self.cfg_connection
def done(self):
""
if __name__=="__main__":
M = Main(talk=1, adhoc_param="nobody is expecting me")
M.explain()
M.work()
M.done()
# ### printout from M.explain()
# #
# # configuration:
# #
# Main.cfg = CfgBunch(
# _instancename = 'Main.cfg',
# adhoc_param = 'nobody is expecting me',
# talk = 1,
# )
# Main.cfg_connection = CfgBunch(
# _instancename = 'Main.cfg_connection',
# host = 'localhost',
# user = 'test',
# )
|
Often one has to set up lots of parameters before the intended actions finally can take place. Especially if functions take an arbitrary number of keyword parameters the organizing, remembering and documentation of those parameters can become major task. In the worst case "configuring" becomes more difficult than "getting the work done". Example: It's easy to issue a database request - once you have provided everything needed to specify "connection", "database", "tables" and "fields".
Parameters may be conceived as key-value pairs. Thus the most natural way to store sets of parameters are dictionaries: that's what they are built for. Unfortunately initializing a dictionary (at least up to Python 2.3) and item access differ from how we usally provide (as argument lists in function calls) and access parameters (as attributes). The BunchDict() class is the cure to this problem. The dictionary may be created from keyword parameters and items can be accessed as attributes. In contrast to Bunch() in recipe 52308 you have the full power of a normal dictionary and may add methods as you like without the risk of names clashing with stored items.
CfgDict() is a further enhancement and has the ability to print itself as Python source. If expressive names are chosen the output becomes pretty much explanatory and forms a solid basis for log-files or ini-files or template-files of Python source. Introspection of the inner workings of a program is facilitated as well. This can be especially helpful in situations were default-, provided- and computed-parameters interact and you like to document or find out about the final set of parameters that comes into effect.
It may be justified to talk of a "change of paradigm": Mainly it's not creating attributes any more (which at times are being copied into a dictionary) but it's using - enhanced - dictionaries right away. What sounds like a minor difference can really change the scene in practice. Together with the habit of chunking parameters in different namespaces it's amazing to experience how complexity can be reduced.
As usual, everything can be improved. Providing a "pretty printed" output of nested CfgBunch-objects is another "nice-to-have" to go for.
simpler implementation of BunchDict. The BunchDict class can be implemented in a much simpler (and faster) way by taking advantage of Alex Martelli's original idiom:
There's no need to implement the __getattr__ and __setattr__ methods. They're taken care of automagically by "self.__dict__ = self". The behavior of this class is identical to that of the original code.