Re: [Webware-discuss] Should Webware adopt the "properties
style"?

At 01:40 PM 12/31/01 -0800, Tavis Rudd wrote:
>One other thing to note is that properties don't work on 'classic'
>classes. All classes that use them must subclass 'object', thus
>becoming a new-style class. I have no idea how this works with
>multiple inheritance and mixins.
From what I've read, new-style classes are supposed to work _better_ with
multiple inheritance because they have a more intelligent method lookup order.
--
- Geoff Talvola
gtalvola@...

Thread view

This topic is about the OO style employed by Webware. It is a long
topic and some may find it boring, but it affects the programmatic
interface to Webware, so I felt it should be posted to -discuss rather
than -devel for all to see.
Here is the main point:
With the advent of Python 2.2, we have the "properties style" as
described here:
http://www.python.org/2.2/descrintro.html#property
Should Webware adopt the "properties style"?
+ Easy to read and write.
+ Would make Webware source more Pythonic.
+ Preserves "opaqueness".
+ As fast as mixed style and faster than other styles.
- Existing code bases would require updates.
- Would required Python 2.2 OR that we provide the GetterSetter helper
for 2.0 and 2.1 users (with the understanding that performance might
take a dive for such users).
And here is a detailed background if you care to read further:
When writing classes, the "mixed style" is to use both data attributes
and methods to access values:
# Vector.py
class Vector:
def __init__(self):
self.x = self.y = 0
def length(self):
return math.sqrt(self.x*self.x + self.y*self.y)
# prog.py
v = Vector()
v.x = 1
v.y = 4
print v.length()
I don't like the mixed style because if you switch a "foo" from a
method to an attribute or the other way around, you break the interface:
class Vector:
def __init__(self):
self.x = self.y = 0
def setX(self, x):
self.x = x
self._computerLength()
def setY(self, y):
self.y = y
self._computeLength()
def _computeLength(self):
self.length = math.sqrt(self.x*self.x + self.y*self.y)
Now prog.py is broken simply because we wanted to compute the length
when X or Y are set (presumably we have determined it is asked for much
more frequently than it is altered). Setting X and Y broke because they
have to be methods now and asking for the length broke because it no
longer requires paren()s.
(Note: These code examples are neither perfect, nor tested. They are
just to illustrate the issues.)
Some people solve this by using the "getter-setter style":
# GetterSetter.py
class GetterSetter:
def __getattr__(self, name):
if not name.startswith('__'):
method = getattr(self, 'get_' + name, None)
if method:
return method()
raise AttributeError, name
def __setattr__(self, name, value):
if not name.startswith('__'):
method = getattr(self, 'set_' + name, None)
if method:
method(value)
self.__dict__[name] = value
# Vector.py
class Vector(GetterSetter):
def __init__(self):
self.x = self.y = 0
def set_x(self, value):
self.__dict__['x'] = value
# ^^ use self.__dict__ to avoid infinite recursion to __setattr__
self._computeLength()
def set_y(self, value):
self.__dict__['y'] = value
self._computeLength()
def _computeLength(self):
self.length = math.sqrt(self.x*self.x + self.y*self.y)
Pros and cons of getter-setter style:
+ Opaque: No code using Vector is broken.
+ Concise: Vector users can conveniently avoid the extra parens and
"set" words and such that come with methods. eg. v.x = 5 and v.length
instead of v.setX(5) and v.length()
- Performance: Having experimented with this style in a recent project,
I found it could be a major source of slow down. Particularly,
__setattr__ adds a method call and extra Python byte code to every
attribute assignment. My program slowed down by at least 10X.
- Implementation: The use of self.__dict__['foo'] is ugly and
forgetting it results in infinite recursion (quickly spotted of course).
- Implementation: __getattr__ and __setattr__ are "asymmetric" in their
design. The first is called as a last resort, the other is always
called. This leads to some subtle issues in how you treat your
attributes.
Next we have the "encapsulation with methods" style which I guess I
took from Smalltalk & Objective-C and is the current Webware style. Use
methods for everything large and small. Pros and cons:
+ No code breaks due to simple interface changes.
+ Faster execution than the getter-setter style.
+ Generally encourages more protection of the object's data attributes.
+ Easy to understand
- Slower execution than the mixed style.
- More typing: obj.setFoo() obj.foo() self._foo
In a recent, private project I started with the getter-setter style and
found it more convenient than method encapsulation, but too expensive
performance-wise. I then scaled back to the mixed style, but with the
problems described above. Overall, I still liked the conciseness of
both styles. I felt like coding was quicker and just as readable.
The properties style seems to give all the pros and only legacy-related
cons. See the beginning of the message.
Reactions?
-Chuck

One of the biggest peformance slowdowns in our current code is the
extensive use of functions to set and get variable values. So anything
that can take away some of the overhead is the way to go in my book.
However, this is a very new capability in Python, and many people will
not have 2.2 installed. Maybe we should wait a while before
implementing this? My only issue is timing, not whether it is the right
thing to do.
Jay
On Mon, 2001-12-31 at 00:54, Chuck Esterbrook wrote:
> This topic is about the OO style employed by Webware. It is a long
> topic and some may find it boring, but it affects the programmatic
> interface to Webware, so I felt it should be posted to -discuss rather
> than -devel for all to see.
>
> Here is the main point:
>
> With the advent of Python 2.2, we have the "properties style" as
> described here:
> http://www.python.org/2.2/descrintro.html#property
>
> Should Webware adopt the "properties style"?
>
> + Easy to read and write.
> + Would make Webware source more Pythonic.
> + Preserves "opaqueness".
> + As fast as mixed style and faster than other styles.
> - Existing code bases would require updates.
> - Would required Python 2.2 OR that we provide the GetterSetter helper
> for 2.0 and 2.1 users (with the understanding that performance might
> take a dive for such users).
>
>
> And here is a detailed background if you care to read further:
>
> When writing classes, the "mixed style" is to use both data attributes
> and methods to access values:
>
> # Vector.py
> class Vector:
> def __init__(self):
> self.x = self.y = 0
> def length(self):
> return math.sqrt(self.x*self.x + self.y*self.y)
>
> # prog.py
> v = Vector()
> v.x = 1
> v.y = 4
> print v.length()
>
> I don't like the mixed style because if you switch a "foo" from a
> method to an attribute or the other way around, you break the interface:
>
> class Vector:
> def __init__(self):
> self.x = self.y = 0
> def setX(self, x):
> self.x = x
> self._computerLength()
> def setY(self, y):
> self.y = y
> self._computeLength()
> def _computeLength(self):
> self.length = math.sqrt(self.x*self.x + self.y*self.y)
>
> Now prog.py is broken simply because we wanted to compute the length
> when X or Y are set (presumably we have determined it is asked for much
> more frequently than it is altered). Setting X and Y broke because they
> have to be methods now and asking for the length broke because it no
> longer requires paren()s.
>
> (Note: These code examples are neither perfect, nor tested. They are
> just to illustrate the issues.)
>
> Some people solve this by using the "getter-setter style":
>
> # GetterSetter.py
> class GetterSetter:
>
> def __getattr__(self, name):
> if not name.startswith('__'):
> method = getattr(self, 'get_' + name, None)
> if method:
> return method()
> raise AttributeError, name
>
> def __setattr__(self, name, value):
> if not name.startswith('__'):
> method = getattr(self, 'set_' + name, None)
> if method:
> method(value)
> self.__dict__[name] = value
>
> # Vector.py
> class Vector(GetterSetter):
>
> def __init__(self):
> self.x = self.y = 0
>
> def set_x(self, value):
> self.__dict__['x'] = value
> # ^^ use self.__dict__ to avoid infinite recursion to __setattr__
> self._computeLength()
>
> def set_y(self, value):
> self.__dict__['y'] = value
> self._computeLength()
>
> def _computeLength(self):
> self.length = math.sqrt(self.x*self.x + self.y*self.y)
>
> Pros and cons of getter-setter style:
> + Opaque: No code using Vector is broken.
> + Concise: Vector users can conveniently avoid the extra parens and
> "set" words and such that come with methods. eg. v.x = 5 and v.length
> instead of v.setX(5) and v.length()
> - Performance: Having experimented with this style in a recent project,
> I found it could be a major source of slow down. Particularly,
> __setattr__ adds a method call and extra Python byte code to every
> attribute assignment. My program slowed down by at least 10X.
> - Implementation: The use of self.__dict__['foo'] is ugly and
> forgetting it results in infinite recursion (quickly spotted of course).
> - Implementation: __getattr__ and __setattr__ are "asymmetric" in their
> design. The first is called as a last resort, the other is always
> called. This leads to some subtle issues in how you treat your
> attributes.
>
>
> Next we have the "encapsulation with methods" style which I guess I
> took from Smalltalk & Objective-C and is the current Webware style. Use
> methods for everything large and small. Pros and cons:
> + No code breaks due to simple interface changes.
> + Faster execution than the getter-setter style.
> + Generally encourages more protection of the object's data attributes.
> + Easy to understand
> - Slower execution than the mixed style.
> - More typing: obj.setFoo() obj.foo() self._foo
>
>
> In a recent, private project I started with the getter-setter style and
> found it more convenient than method encapsulation, but too expensive
> performance-wise. I then scaled back to the mixed style, but with the
> problems described above. Overall, I still liked the conciseness of
> both styles. I felt like coding was quicker and just as readable.
>
> The properties style seems to give all the pros and only legacy-related
> cons. See the beginning of the message.
>
>
> Reactions?
>
> -Chuck
>
> _______________________________________________
> Webware-discuss mailing list
> Webware-discuss@...
> https://lists.sourceforge.net/lists/listinfo/webware-discuss

At 01:40 PM 12/31/01 -0800, Tavis Rudd wrote:
>One other thing to note is that properties don't work on 'classic'
>classes. All classes that use them must subclass 'object', thus
>becoming a new-style class. I have no idea how this works with
>multiple inheritance and mixins.
From what I've read, new-style classes are supposed to work _better_ with
multiple inheritance because they have a more intelligent method lookup order.
--
- Geoff Talvola
gtalvola@...

At 01:50 PM 12/31/01 -0800, Tavis Rudd wrote:
>and one more thing ... the __bases__ attribute in new-style classes
>is read-only. This would prevent Chuck's Mixin.py from working and
>might cause problems with WebKit's plugin architecture.
Chuck's Mixin code only assigns to __bases__ if makeAncestor=1 is passed in
as an argument, and the docstring says:
"If makeAncestor is 1, then a different technique is employed: the
mixInClass is made the first base class of the pyClass. You probably don't
need to use this and if you do, be aware that your mix-in can no longer
override attributes/methods in pyClass."
So not being able to assign to __bases__ shouldn't hurt us, as it only
affects an option we're not supposed to use :-)
Maybe Chuck could explain the purpose of the makeAncestor option -- I'm not
sure why it's there at all.
--
- Geoff Talvola
gtalvola@...

On Monday 31 December 2001 01:19 pm, Geoffrey Talvola wrote:
> At 01:50 PM 12/31/01 -0800, Tavis Rudd wrote:
> >and one more thing ... the __bases__ attribute in new-style classes
> >is read-only. This would prevent Chuck's Mixin.py from working and
> >might cause problems with WebKit's plugin architecture.
>
> Chuck's Mixin code only assigns to __bases__ if makeAncestor=1 is
> passed in as an argument, and the docstring says:
>
> "If makeAncestor is 1, then a different technique is employed: the
> mixInClass is made the first base class of the pyClass. You probably
> don't need to use this and if you do, be aware that your mix-in can
> no longer override attributes/methods in pyClass."
>
> So not being able to assign to __bases__ shouldn't hurt us, as it
> only affects an option we're not supposed to use :-)
>
> Maybe Chuck could explain the purpose of the makeAncestor option --
> I'm not sure why it's there at all.
It is there purely for legacy reasons. Assigning to __bases__ was the
first approach of MixIn and is described in the associated article (I
believe).
In practice, I have never needed that option and we can easily drop it
unless someone has a particular objection.
-Chuck