Recipe4.18.Collecting a Bunch of Named Items


Recipe 4.18. Collecting a Bunch of Named Items

Credit: Alex Martelli, Doug Hudgeon

Problem

You want to collect a bunch of items together, naming each item of the bunch, and you find dictionary syntax a bit heavyweight for the purpose.

Solution

Any normal class instance inherently wraps a dictionary, which it uses to hold its state. We can easily take advantage of this handily wrapped dictionary by coding a nearly empty class:

class Bunch(object):     def _ _init_ _(self, **kwds):         self._ _dict_ _.update(kwds)

Now, to group a few variables, create a Bunch instance:

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

You can now access and rebind the named attributes just created, add others, remove some, and so on. For example:

if point.squared > threshold:     point.isok = True

Discussion

We often just want to collect a bunch of stuff together, naming each item of the bunch. A dictionary is OK for this purpose, but a small do-nothing class is even handier and prettier to use.

It takes minimal effort to build a little class, as in this recipe, to provide elegant attribute-access syntax. While a dictionary is fine for collecting a few items in which each item has a name (the item's key in the dictionary can be thought of as the item's name, in this context), it's not the best solution when all names are identifiers, to be used just like variables. In class Bunch's _ _init_ _ method, we accept arbitrary named arguments with the **kwds syntax, and we use the kwds dictionary to update the initially empty instance dictionary, so that each named argument gets turned into an attribute of the instance.

Compared to attribute-access syntax, dictionary-indexing syntax is not quite as terse and readable. For example, if point was a dictionary, the little snippet at the end of the "Solution" would have to be coded like:

if point['squared'] > threshold:     point['isok'] = True

An alternative implementation that's just as attractive as the one used in this recipe is:

class EvenSimplerBunch(object):     def _ _init_ _(self, **kwds):         self._ _dict_ _ = kwds

Rebinding an instance's dictionary may feel risqué, but it's not actually any pushier than calling that dictionary's update method. So you might prefer the marginal speed advantage of this alternative implementation of Bunch. Unfortunately, I cannot find anywhere in Python's documentation an assurance that usage like:

d = {'foo': 'bar'} x = EvenSimplerBunch(**d)

will forever keep making x._ _dict_ _ an independent copy of d rather than just sharing a reference. It does currently, and in every version, but unless it's a documented semantic constraint, we cannot be entirely sure that it will keep working forever. So, if you do choose the implementation in EvenSimplerBunch, you might choose to assign a copy (dict(kwds) or kwds.copy( )) rather than kwds itself. And, if you do, then the marginal speed advantage disappears. All in all, the Bunch presented in this recipe's Solution is probably preferable.

A further tempting but not fully sound alternative is to have the Bunch class inherit from dict, and set attribute access special methods equal to the item access special methods, as follows:

class DictBunch(dict):     _ _getattr_ _ = dict._ _getitem_ _     _ _setattr_ _ = dict._ _setitem_ _     _ _delattr_ _ = dict._ _delitem_ _

One problem with this approach is that, with this definition, an instance x of DictBunch has many attributes it doesn't really have, because it inherits all the attributes (methods, actually, but there's no significant difference in this context) of dict. So, you can't meaningfully check hasattr(x, someattr), as you could with the classes Bunch and EvenSimplerBunch previously shown, unless you can somehow rule out the value of someattr being any of several common words such as 'keys', 'pop', and 'get'.

Python's distinction between attributes and items is really a wellspring of clarity and simplicity. Unfortunately, many newcomers to Python wrongly believe that it would be better to confuse items with attributes, generally because of previous experience with JavaScript and other such languages, in which attributes and items are regularly confused. But educating newcomers is a much better idea than promoting item/attribute confusion.

See Also

The Python Tutorial section on classes; the Language Reference and Python in a Nutshell coverage of classes; Chapter 6 for more information about object-oriented programming in Python; Recipe 4.18 for more on the **kwds syntax.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net