G
Gabriel Genellina
* Steven D'Aprano:How would it have avoided the problem? Either of these would have theFor example, consider two rectangle classes R1 and R2, where R2 might
be a successor to R1, at some point in system evolution replacing R1.
R1 has logical data members left, top, width and height, and R2 has
logical data members left, top, right and bottom. With R1 direct
changes of left and top keeps the rectangle's size (since that size
is
specified by width and height), while with R2 it changes the
rectangle's size. R1 is implemented with logical data members as
directly exposed data attributes. Some code in the system deals only
with R1 objects and for convenience or efficiency or whatever uses
direct modification instead of set_position method. Due to new
requirements it instead has to deal with R2 objects, with same
methods. But due to the direct modification of object state it now
changes the rectangle sizes, but at first it's not noticed since the
attempted rectangle position changes are very small. People get
upset.
The bug is fixed. Time has been wasted.
If there is need for mutable rectangles, there is need for mutable
rectangles. Using properties instead of attributes doesn't help
In the example above using properties would have avoided the problem.
exact same semantics:
class R2(object):
def __init__(self):
self._secret = {'top': 0, 'bottom': 100}
def _top_getter(self):
return self._secret['top']
def _top_setter(self, value):
self._secret['top'] = value
top = property(_top_getter, _top_setter)
vs
class R2(object):
def __init__(self):
self.top = 0
self.bottom = 100
OK, I had a laugh. You maintain that all doors are dark red, and
show up a photo of two of your dark red doors as proof.
For R2, did you at *any* moment consider properties that emulated the
internal representation of R1?
I'm putting it that way on the off-chance that you're not just
pretending to not understand.
I don't understand either. R1 and R2 have *different* semantics. They
don't behave the same. Any breakage you experiment using R2 instead of R1
comes from the fact they behave differently, not because they're
implemented differently, nor because they use properties or not.
You could have implemented another variant, let's say RCrazy, that behaves
exactly the same as R1 but internally stores a different set of attributes
(the baricenter, the angle between both diagonals, and the diagonal
length). As long as you implement the same public interfase (same set of
externally visible attributes, same methods, same behavior) RCrazy is
interchangeable with R1; RCrazy is a subtype of R1 in the sense of the
Liskov substitution principle.
Of course, perfect substitutability (did I spell it right?) isn't possible
in Python; obj.__class__.__name__ returns 'RCrazy' instead of 'R1', it's
mro() is different, dir() returns a different set of names, etc. But for
any `reasonable` use of an R1 instance representing a rectangle, an RCrazy
instance should serve equally well.
No semantics was specified in my example, quoted in full above.
However, the natural semantics is that various logical properties, such
as left, top, right, bottom, width and height, can be varied
independently.
You did specify the set of attributes and how they behave, perhaps not
very formally, but that's a semantic specification to me. It was clear
from your description that R2 behave different that R1; the problems that
came after using R2 instead of R1 were caused by such different behavior,
not because of using properties or not. In any case, I don't think the
problem is specific to Python.
Happily that's incorrect. You might try to consider what properties are
*for*, why the language supports them if they do nothing at all except
adding overhead.
In normal usage (either obj.name or getattr(obj, 'name')) an attribute is
undistinguishable from a property. Users of the class should not worry at
all whether it is implemented as a simple attribute, a computed property,
or an esoteric class attribute following the descriptor protocol.
If all a property does is to store and retrieve its value as an instance
attribute, yes, it just adds overhead.