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):Now, to group a few variables, create a Bunch
def _ _init_ _(self, **kwds):
self._ _dict_ _.update(kwds)
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:An alternative implementation that's just as
point['isok'] = True
attractive as the one used in this recipe is:
class EvenSimplerBunch(object):Rebinding an instance's dictionary may feel
def _ _init_ _(self, **kwds):
self._ _dict_ _ = kwds
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'}will forever keep making x._ _dict_ _ an independent
x = EvenSimplerBunch(**d)
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):One problem with this approach is that, with this definition, an
_ _getattr_ _ = dict._ _getitem_ _
_ _setattr_ _ = dict._ _setitem_ _
_ _delattr_ _ = dict._ _delitem_ _
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.