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

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, 14 lines
 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

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.

14 comments

Doug Hudgeon 22 years, 8 months ago  # | flag

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>

linden 20 years, 2 months ago  # | flag

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.

linden 20 years, 2 months ago  # | flag

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.

Graham Fawcett 19 years, 7 months ago  # | flag

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.

Simon Brunning 19 years, 4 months ago  # | flag

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)
Magnus Lie Hetland 18 years, 8 months ago  # | flag

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 :)

Ben North 18 years, 6 months ago  # | flag

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.)

Ben North 18 years, 6 months ago  # | flag

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

"One dictionary is better than two", Graham Fawcett

Yair Chuchem 18 years, 4 months ago  # | flag

CAUTION. this one leaks memory just try:

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

you need to stay away from circular references

Yair Chuchem 18 years, 4 months ago  # | flag

CAUTION. this one leaks memory

just try:

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

... a = Bunch()

you need to stay away from circular references

Yair Chuchem 18 years, 4 months ago  # | flag

CAUTION. as noted before - this leaks memory!

Robin Bryce 18 years, 1 month ago  # | flag

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
Robin Siebler 14 years, 10 months ago  # | flag

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

Éric Araujo 14 years, 2 months ago  # | flag

You are using iterkeys, which iterates over keys, not over key-value pairs. That’s what produces the ValueError: The first element from “fruit.iterkeys()” is not a sequence, so Python cannot unpack it to “k, v”.

Cheers

Created by Alex Martelli on Mon, 26 Mar 2001 (PSF)
Python recipes (4591)
Alex Martelli's recipes (27)

Required Modules

  • (none specified)

Other Information and Tasks