A matter of style: class.foo = bar vs. class.set_foo(bar)

Chuck Esterbrook echuck at mindspring.com
Tue Nov 14 10:16:00 EST 2000


Thanks for the example. I was just writing GetterSetter last night and 
using a Vector example where the length was maintained as x and y were set. 
Interesting that we both chose geometry examples, although I think yours is 
more cogent.

In any case, I came to the following realization which turned me off to the 
technique. Tell me what you think:

__getattr__ is only invoked if the attribute cannot be found by normal 
means. In other words, if obj.foo cannot be found then 
obj.__getattr__('foo') is invoked. That means that if obj inherits from A 
which inherits B which inherits C, the following operations occur:

         search obj for foo
         search A for foo
         search B for foo
         search C for foo
         invoke __getattr__('foo')

And the more classes you have the worse it gets. The performance here could 
be really, really bad. I know some say "performance doesn't matter in 
Python", but I find that in all software it matters to a degree. Just 
because your code runs slow doesn't mean you want it to get even worse.

I wish Python had a version of __getattr__ which was always invoked 
immediately. Or this would work well if Python had this language feature 
built-in. e.g., if setting, getting and deleting foo always invoked 
set_foo(), get_foo() and del_foo() if they existed.

And for that matter I wish it had class methods and a unified class hierarchy.

If only I had more time.  :-)


-Chuck
--
Webware for Python:
http://webware.sourceforge.net


At 07:44 PM 11/14/00 +1300, Paul Foley wrote:
>On Tue, 14 Nov 2000 04:50:33 GMT, echuck  wrote:
>
> >  "Alex Martelli" <aleaxit at yahoo.com> wrote:
> >> <echuck at mindspring.com> wrote
> >> [snip]
> >> > The disadvantage of:
> >> >   obj.foo = 5
> >> >
> >> > is that in the future if you want to take some action when foo is set,
> >> > like asserting that it's in range or not None, then you're out of luck.
> >>
> >> Naah -- you just have to add a __setattr__ method (or a clause to
> >> your existing __setattr__ method); or, with suitable metaclasses
> >> (such as those in py_cpp), you even get to define a specific
> >> __setattr__foo method to catch the specific setting of obj.foo only.
>
> > Although it's technically feasible to do a __setattr__, what would the
> > results look like for foo, bar, a, b, c?
>
> >    if name=="foo":
> >       do_this()
> >    elif name=="bar":
> >       do_that()
> >    elif name=="...
>
> > You get the drift. This is really inefficient. Every time you add a new
> > attribute you increase this linear search and pile on more code in
> > __setattr__. I suppose you could do this:
>
> >    methodName = "set_"+name
> >    method = getattr(self, methodName, None)
> >    if method:
> >       method(name, value)
> >    else:
> >       self.__dict__[name] = value
>
> > But it feels silly to make setattr to call set_foo() for you...maybe
> > I'm just not use to it.
>
>I have, in my ~/python directory, a file I wrote as an example for
>someone asking a similar question quite a while back.  The contents of
>that file are:
>
>   import math
>
>   class Point(GetterSetter):
>       def __init__(self, x=0, y=0, r=None, phi=None):
>           if r and phi:
>              self.set_polar(r, phi)
>           else:
>              self.x, self.y = x, y
>       def set_polar(self, r, phi):
>           self.x = r*math.cos(phi)
>           self.y = r*math.sin(phi)
>       def _rd_r(self):
>           return math.sqrt(self.x**2 + self.y**2)
>       def _rd_phi(self):
>           return math.atan2(self.y, self.x)
>       def _wr_r(self, r):
>           self.set_polar(r, self.phi)
>       def _wr_phi(self, phi):
>           self.set_polar(self.r, phi)
>
>Unfortunately, I didn't keep the file implementing the "GetterSetter"
>class (I must have just typed it in my reply and pasted it at the
>prompt, based on the lack of "import" statement for it,
>above...reconstructing it would be a good exercise, I suppose), but
>you can see that it did essentially what you've written above: turned
>uses of, and assignments to, point.r and point.phi, for some instance
>point of Point, into calls to _rd_r()/_rd_phi() and _wr_r()/_wr_phi()
>respectively, so that both cartesian and polar coordinates are
>available and kept consistent while providing a nice "point.r = 6"
>interface instead of "point.set_r(6)" or something equally ugly.
>
>[FWIW, the GetterSetter mixin only rewrote calls to the _rd_* and
>_wr_* functions if they actually existed; that's why point.x and
>point.y work without having to write _rd_x() and _wr_x(), etc.]
>
>--
>You don't have to agree with me; you can be wrong if you want.
>
>(setq reply-to
>   (concatenate 'string "Paul Foley " "<mycroft" '(#\@) "actrix.gen.nz>"))





More information about the Python-list mailing list