ActiveState Code

Recipe 52308: The simple but handy "collector of a bunch of named stuff" class


Often we want to just collect a bunch of stuff together, naming each item of the bunch; a dictionary's OK for that, but a small do-nothing class is even handier, and prettier to use.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Bunch:
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

# that's it!  Now, you can create a Bunch
# whenever you want to group a few variables:

point = Bunch(datum=y, squared=y*y, coord=x)

# and of course you can read/write the named
# attributes you just created, add others, del
# some of them, etc, etc:
if point.squared > threshold:
    point.isok = 1

Discussion

Dictionaries are fine for collecting a small bunch of stuff, each item with a name; however, when names are constants and to be used just like variables, the dictionary-access syntax ("if bunch['squared'] > threshold", etc) is not maximally clear; it takes VERY little effort to build a little class, as in the 'Bunch' example above, that will both ease the initialization task _and_ provide elegant attribute-access syntax ("if bunch.squared > threshold", etc).

It would not be hard to add __getitem__, __setitem__ and __delitem__ methods to allow attributes to also be accessed as bunch['squared'] etc -- they would just have to delegate to self.__dict__, e.g. via the handy functions getitem, setitem, delitem in module operator. This would mimic javascript use, where attributes and items are regularly confused; such unPythonic idioms, however, seem to be completely useless in Python. For occasional access to an attribute whose name is held in a variable (or otherwise runtime-computed), builtin functions getattr, setattr and delattr are perfectly adequate, and definitely preferable to complicating the delightfully-simple Bunch class.

Comments

  1. 1. At 3:50 a.m. on 29 aug 2001, Doug Hudgeon said:

    Even shorter Bunch.

    class Bunch:
        __init__ = lambda self, **kw: setattr(self, '__dict__', kw)
    <pre>
    
    You could also accept *args and put them in a sequence accessible
    with [].
    
    When python 2.2 permits the above dynamic __dict__ this could be
    be something like:
    
    <pre>
    class Bunch(list):
        def __init__(*args, **kw):
            self[:] = list(args)
            setattr(self, '__dict__', kw)
    <pre>
    

    </pre></pre></pre>

  2. 2. At 7:24 a.m. on 19 feb 2004, Anonymous said:

    Using a bunch as a dictionary. Quite simple extension of the original Bunch, just one extra line:

    class BunchDict(dict):
        def __init__(self,**kw):
            dict.__init__(self,kw)
            self.__dict__.update(kw)
    

    This has the added benefit that it can directly be printed and it shows its contents in interactive environments like ipython.

  3. 3. At 7:26 a.m. on 19 feb 2004, Anonymous said:

    Using a bunch as a dictionary. Quite simple extension of the original Bunch, just one extra line:

    class BunchDict(dict):
        def __init__(self,**kw):
            dict.__init__(self,kw)
            self.__dict__.update(kw)
    

    This has the added benefit that it can directly be printed and it shows its contents in interactive environments like ipython.

  4. 4. At 6:06 a.m. on 8 oct 2004, Graham Fawcett said:

    One dictionary is better than two. How about just replacing self.__dict__ with self, since self is a dict?

    class Bunch(dict):
        def __init__(self,**kw):
            dict.__init__(self,kw)
            self.__dict__ = self
    

    Then any deletions, modifications, etc. are accessible via both the dict and attribute interfaces.

  5. 5. At 2:21 a.m. on 31 dec 2004, Simon Brunning said:

    String representation. For debugging purposes, it's nice to have a meaningful string representation. This works for me:

    def __str__(self):
        state = ["%s=%r" % (attribute, value)
                 for (attribute, value)
                 in self.__dict__.items()]
        return '\n'.join(state)
    
  6. 6. At 7:14 a.m. on 1 sep 2005, Magnus Lie Hetland said:

    Using dict functionality. You could simply do something like this, given the current functionality of dict (with kwd args etc.):

    class bunch(dict):
        __getattr__ = dict.__getitem__
    

    Of course, you won't get the right exceptions etcl., and simply setting __setattr__ the same way won't really work. But it seems to be about as simple as this recipe can get :)

  7. 7. At 5:13 a.m. on 2 nov 2005, Ben North said:

    Improvement to handle pickling. As written, pickling and unpickling such an object breaks the equivalence between self and self.__dict__. Adding the methods __getstate__ and __setstate__ used by the pickle protocol fixes this:

    class Bunch(dict):
        def __init__(self, **kw):
            dict.__init__(self, kw)
            self.__dict__ = self
    
        def __getstate__(self):
            return self
    
        def __setstate__(self, state):
            self.update(state)
            self.__dict__ = self
    

    (Perhaps there's a better way to do this, but it seems to have the desired effect.)

  8. 8. At 5:16 a.m. on 2 nov 2005, Ben North said:

    Ugh; follow-up in wrong place. Apologies; that was supposed to be a comment on

    "One dictionary is better than two", Graham Fawcett

  9. 9. At 4 a.m. on 27 dec 2005, Yair Chuchem said:

    CAUTION. this one leaks memory just try:

    >>> for i in range(10**9):
    ...     a = Bunch()
    

    you need to stay away from circular references

  10. 10. At 4 a.m. on 27 dec 2005, Yair Chuchem said:

    CAUTION. this one leaks memory

    just try:

    >>> for i in range(10**9):
    

    ... a = Bunch()

    you need to stay away from circular references

  11. 11. At 4:02 a.m. on 27 dec 2005, Yair Chuchem said:

    CAUTION. as noted before - this leaks memory!

  12. 12. At 10:10 a.m. on 2 apr 2006, Robin Bryce said:

    Is there an issue with using the type builtin for this ? If your use case is accessing **kw using dot notation then how about:

    kw=type('bunch',(), kw)

    Or, more closely following the 'utility helper' style of the original:

    def bunch(**kw):
        return type('bunch',(), kw)
    
    B=bunch(apples=1,pears=2)
    b=B()
    assert B.apples == b.apples and b.apples=1
    
    class Y(B)
        bannanas=3
        pears=19
    
    y=Y()
    
    assert y.pears == Y.pears and y.pears != b.pears
    
  13. 13. At 6:46 p.m. on 11 jun 2009, Robin Siebler said:

    class Bunch(dict):

    def __init__(self,**kw):
        dict.__init__(self,kw)
        self.__dict__ = self
    
    fruit = Bunch(apples = 1, pears = 2)
    for k,v in fruit.iterkeys():
        print k,v
    

    The above results in the below error. Is there a way to get the ease of use AND the ability to iterate through the values? ValueError: too many values to unpack

Sign in to comment