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

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

Python, 168 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
 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.

1 comment

David Goodger 18 years, 7 months ago  # | flag

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:

class BunchDict(dict):

    def __new__(cls, **kwargs):
        self = dict.__new__(cls, kwargs)
        self.__dict__ = self
        return self

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.

Created by Martin Bless on Fri, 3 Sep 2004 (PSF)
Python recipes (4591)
Martin Bless's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks