Getters and Setters
Andrew Dalke
dalke at bioreason.com
Thu Jul 15 03:30:41 EDT 1999
Neil Schemenauer <nascheme at ucalgary.ca> proposed:
> # A mixin class to automagicly create getter/setter methods for
> # class attributes.
> class GetterSetter:
> def __getattr__(self, name):
> try:
> if name[:3] == 'get':
> return _Getter(self.__dict__[name[3:]])
> elif name[:3] == 'set':
> return _Setter(self, name[3:])
> except KeyError:
> pass
> raise AttributeError
Here's one optimization, don't recreate getter/setter objects.
How about this:
class GetterSetter:
def __getattr__(self, name):
try:
if name[:3] == 'get':
x = _Getter(self.__dict__[name[3:]])
self.__dict__[name] = x
return x
elif name[:3] == 'set':
x = _Setter(self, name[3:])
self.__dict__[name] = x
return x
except KeyError:
pass
raise AttributeError
For your timing test that reduced the overhead to a factor of
about 2.5 (I upped n to 10000 to get better times on my machine):
Your version
val> python getattr.py
Comparing 10000 iterations
GetterSetter 1.78183102608
explicit function 0.201846957207
Cached objects
val> python getattr.py
Comparing 10000 iterations
GetterSetter 0.455724000931
explicit function 0.207740902901
Still, you're doing this __call__ lookup every time, so why
not use something which doesn't need that lookup, like a function?
My first thought was to create a lambda function or a function
in local scope, but the argument passing trick (lamba val=val: val)
would mess up the API by allowing an argument to be passed in.
Since your timing test does so many repeats (esp. when I increase
n :), that suggests I can take a 1-time cost per attribute, so
I can use an eval.
class GetterSetter:
def __getattr__(self, name):
try:
if name[:3] == 'get':
#x = _Getter(self.__dict__[name[3:]])
val = self.__dict__[name[3:]]
x = eval("lambda :x", {"x": val})
self.__dict__[name] = x
return x
elif name[:3] == 'set':
x = _Setter(self, name[3:])
self.__dict__[name] = x
return x
except KeyError:
pass
raise AttributeError
val> python getattr.py
Comparing 10000 iterations
GetterSetter 0.359815955162
explicit function 0.210291981697
Now, only 70% slower.
For my final trick, I'll do the same to the Setter:
import operator
class GetterSetter:
def __getattr__(self, name):
try:
if name[:3] == 'get':
#x = _Getter(self.__dict__[name[3:]])
val = self.__dict__[name[3:]]
x = eval("lambda :x", {"x": val})
self.__dict__[name] = x
return x
elif name[:3] == 'set':
x = eval("lambda val: set(dict, %s, val)" % `name[3:]`,
{"dict": self.__dict__, "set": operator.setitem})
#x = _Setter(self, name[3:])
self.__dict__[name] = x
return x
except KeyError:
pass
raise AttributeError
Notice that there are now no helper classes, so you can mitigate
some of the eval() time by not doing the class creation + __init__.
The results?
val> python getattr.py
Comparing 10000 iterations
GetterSetter 0.236621975899
explicit function 0.20987200737
It's only about 10% slower, though for n==10 it's still about
8 times slower. And it has a nasty case of cyclical references
(since __dict__ caches the lambda functions, which contain
a reference to __dict__). I'll leave cleaning that up as an
exercise to the student :)
Andrew Dalke
dalke at acm.org
More information about the Python-list
mailing list