1.8 Collecting a Bunch of Named Items
Credit: Alex Martelli
1.8.1 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.
1.8.2 Solution
Any (classic) class inherently wraps a dictionary, and we take
advantage of this:
class Bunch:
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 access and rebind the named attributes just created, add
others, remove some, and so on. For example:
if point.squared > threshold:
point.isok = 1
1.8.3 Discussion
Often, we just want to collect a bunch of stuff together, naming each
item of the bunch; a dictionary's okay for that, but
a small do-nothing class is even handier and is prettier to use.
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).
However, when all names are identifiers, to be used just like
variables, the dictionary-access syntax is not maximally clear:
if point['squared'] > threshold
It takes minimal effort to build a little class, as in this recipe,
to ease the initialization task and provide elegant attribute-access
syntax:
if bunch.squared > threshold
An equally attractive alternative implementation to the one used in
the solution is:
class EvenSimplerBunch:
def _ _init_ _(self, **kwds): self._ _dict_ _ = kwds
The alternative presented in the Bunch class has
the advantage of not rebinding self._ _dict_ _ (it
uses the dictionary's update
method to modify it instead), so it will keep working even if, in
some hypothetical far-future dialect of Python, this specific
dictionary became nonrebindable (as long, of course, as it remains
mutable). But this EvenSimplerBunch is indeed even
simpler, and marginally speedier, as it just rebinds the dictionary.
It is not difficult to add special methods to allow attributes to be
accessed as bunch['squared'] and so on. In Python
2.1 or earlier, for example, the simplest way is:
import operator
class MurkierBunch:
def _ _init_ _(self, **kwds):
self._ _dict_ _ = kwds
def _ _getitem_ _(self, key):
return operator.getitem(self._ _dict_ _, key)
def _ _setitem_ _(self, key, value):
return operator.setitem(self._ _dict_ _, key, value)
def _ _delitem_ _(self, key):
return operator.delitem(self._ _dict_ _, key)
In Python 2.2, we can get the same effect by inheriting from the
dict built-in type and delegating the other way
around:
class MurkierBunch22(dict):
def _ _init_ _(self, **kwds): dict._ _init_ _(self, kwds)
_ _getattr_ _ = dict._ _getitem_ _
_ _setattr_ _ = dict._ _setitem_ _
_ _delattr_ _ = dict._ _delitem_ _
Neither approach makes these Bunch variants into
fully fledged dictionaries. There are problems with each�for
example, what is someBunch.keys supposed to mean?
Does it refer to the method returning the list of keys, or is it just
the same thing as someBunch['keys']?
It's definitely better to avoid such confusion:
Python distinguishes between attributes and items for clarity and
simplicity. However, many newcomers to Python do believe they desire
such confusion, generally because of previous experience with
JavaScript, in which attributes and items are regularly confused.
Such idioms, however, seem to have little usefulness in Python. For
occasional access to an attribute whose name is held in a variable
(or otherwise runtime-computed), the built-in functions
getattr, setattr, and
delattr are quite adequate, and they are
definitely preferable to complicating the delightfully simple little
Bunch class with the semantically murky approaches
shown in the previous paragraph.
1.8.4 See Also
The Tutorial section on classes.
|