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