Experiment: Replace __setattr__ With A Descriptor

As part of daily life I see a ton of Python code. Between the code at work and all of the open source projects I watch I'm surprised my eyes don't bleed. Over the years my style has changed dramatically. I guess you could say I am getting more Pythonic. Looking back at old code sometimes just make me feel ill. I know better now. There was a project that I worked on where we wanted to ensure all attributes were typed properly. (No comments about this - it's not the point of the post) To do this we used an evil combination of Hungarian notation and __setattr__ magic. An example class looks something like this:
class Data(object):

    def __init__(self, strX='', fltY=0.0, intZ=0):
        self.strX = strX
        self.fltY = fltY
        self.intZ = intZ

    def __setattr__(self, name, value):
        if name.startswith('int'):
            object.__setattr__(self, name, int(value))
        elif name.startswith('flt'):
            object.__setattr__(self, name, float(value))
        elif name.startswith('str'):
            object.__setattr__(self, name, str(value))
        else:
            object.__setattr__(self, name, value)
As ugly as it is it actually works. All attributes are forced to a type based off of their name. I know the __setattr__ is rather slow, but I never really gave it a second thought even thought I periodically see this code. This weekend a neuron misfired and I had an idea. So I gave this a whirl:
class TypedAttr(object):

    def __init__(self, _type, value):
        self._type = _type
        self._default = self._value = value

    def __get__(self, obj, _type):
        return self._value

    def __set__(self, obj, value):
        self._value = self._type(value)

    def __delete__(self, obj):
        self._value = self._default

class Data(object):
    strX = TypedAttr(str, '')
    fltY = TypedAttr(float, 0.0)
    intZ = TypedAttr(int, 0)
This Data class is must cleaner. The TypedAttr can be defined in another module making this code much smaller and more declarative. The descriptor approach out performs the __getattr__. My tests were using the timeit module in Python 2.5. Running the old way gave me about 25 usec and the new way was 11 usec. I think I'll suggest the change :-)
blog comments powered by Disqus