Wednesday, November 2, 2011

Loving the bunch class

Warning

This is me playing around with things in Python. It's not anything I use in real projects (except maybe the odd test). Please don't use these in anything important or you'll regret it.
Every play with a bunch class? I love 'em and make them protected or unprotected. I started using them early in my Python career, although it wasn't nearly about 2 years ago that I learned what they were called and the best way to code them. In any case, here is a simple, unprotected Bunch class.
# Simple unprotected Python bunch class
class Bunch(object):

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

bunch = Bunch(name='Loving the bunch class')
print(bunch.name)

You can also make protected ones, that don't let pesky developers like me overwrite attributes, methods, and properties by accident:

# Simple protected Python bunch class
class ProtectedBunch(object):
    """ Use this when you don't want to overwrite existing methods and data """

    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            if k not in self.__dict__:
                self.__dict__[k] = v

You can also write them to raise errors when a key is in self.__dict__. Or perhaps merely publish a warning. There are many ways to customize, but generally you want to keep these things as simple as possible. Anyway, let's get back to the main topic...

In the early days of my experiences with Python I found a small, nagging issue with dictionaries and objects. The notation wasn't as handy as what you got with JavaScript and some other languages I was using at the time. For example:
// JavaScript object notation
o = {};
o.name = 'Loving the bunch class';
o.name; // Calling with 'dot' notation
o['name']; // Calling with 'bracket' notation  

Unfortunately, in Python you can't do this with a normal bunch class:
# Python bunch class failing on bracket notation
class Bunch(object):

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

bunch = Bunch(name='Loving the bunch class')
print(bunch.name)
print(bunch['name'])
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'Bunch' object is not subscriptable

The quick answer is found with a little trick I found in the comments of a recipie by Alex Martelli that gives you the ability to do:
# Fancy dictionary/object trick
class Buncher(dict):
    """ Warning: DON'T USE THIS IN REAL PROJECTS """

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

bunch = Bunch(name='Loving the bunch class')
print(bunch.name)
print(bunch['name'])

I'm not the only one who likes Bunch classes. On PyPI I found a really complete implementation.

Of course, in a lot of cases you probably don't want this 'weight of code', right? Dictionaries being lighter than full objects and all that. Nevertheless, it's fun for noodling and playing around with code. Still, I'm thinking it might be a fun little project to take a group of bunch implementations and do performance checks on them versus each other and dictionaries. Maybe the 'performance hit' isn't so bad.

I should also dig into things like defaultdict and other constructs to learn more. Part of the fun of any programming language is the depth of even the 'simplest' components of the language.

2 comments:

Marius Gedminas said...

A check of "k not in self.__dict__" does not prevent you from overriding existing methods, because class attributes do not live in the instance __dict__. Use hasattr() instead.

void said...

It is possible to make a bus model from a loaf of bread. But why?