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:
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 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)
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 :-)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)