[Edu-sig] Design patterns
Scott David Daniels
Scott.Daniels at Acm.Org
Sun Aug 21 19:33:12 CEST 2005
Arthur wrote:
>>What beyond sugar for leaving off a "()" when trying to retrieve a value
>>from a method are we accomplishing by using properties? I have tended to
>>look at properties mostly an accommodation to those coming from other
>>languages which have something similar, but never as something that was
>>core
>>to Python or a Pythonic approach to things. Am I missing something
>>fundamental?
>
>
> The searching I do on this point only confirms to me that my own confusion
> is well shared - perhaps indicating this to be an area not totally OT for
> this forum.
>
> Ray Hettinger's "How-To Guide for Descriptors"
>
> http://users.rcn.com/python/download/Descriptor.htm
>
> covers properties, but the use case given is extremely unsatisfying -
> essentially offering a situation where a fundamental design change has been
> made to a program in mid-stream, and since properties were used in the
> initial design, the change can be made with little refactoring.
I'd propose two reasons why properties are so successful.
The first explanation comes from eXtreme Programming. One big goal of
XP is to stop wasting time doing "big design up front." I've spent
enough time in companies where long design meetings (months) produced
huge design documents. The documents themselves had a tendency to look
like everything was nailed down, while not really answering a lot of
questions that had to be solved when writing the code. The length of
time spent producing the document, and the general unavailability of the
group that wrote it (they typically moved into other committees to write
other design documents), led to an increasingly rigid design that
reflected neither discoveries or innovations from the coders nor changes
in requirements (or at least our understanding of those requirements).
XP can be seen as a reaction to that problem.
The second explanation is much lower level. In O-O code, there is a
distinction between interface and implementation. Essentially, the
interface to an object is how the object behaves from a user of that
object's point of view. The implementation is how that object
accomplishes its behavior. When you separate these concerns, you
can more likely keep a programs complexity (in the sense of debugging/
extending) from growing exponentially with the number of lines of code.
Properties let you to hide the trade-off between accessing a stored
value and computing it on the fly. Without properties, the interface
to classes that want to reserve the access/calculate tradeoff must
use the Java-like "getVar" functions to fetch any values that might be
calculated.
> What would it take to create a @property decorator that allows one
> to set as well as get? Would we want to?
def writeprop(viewvar):
'''decorator makes a property from access and a write method'''
def view(self):
return getattr(self, viewvar)
def buildprop(writemethod):
return property(view, writemethod)
return buildprop
Triangle as an example:
import math
class BaseTriangle(object):
@classmethod
def check(klass, a, b, c):
'''Check three lengths for suitability as triangle sides'''
if a >= b + c:
raise ValueError, 'Too long: %s >= %s + %s' % (a, b, c)
if a <= abs(b - c):
raise ValueError, 'Too short: %s <= abs(%s - %s)' % (a, b,c)
def __init__(self, a, b, c):
self.check(a, b, c)
self._a = a
self._b = b
self._c = c
self._reset()
def __repr__(self):
return '%s(%s, %s, %s)' % (self.__class__.__name__,
self.a, self.b, self.c)
def _reset(self):
'''Called whenever the sides of the triangle change'''
pass
@writeprop('_a')
def a(self, v):
self.check(v, self.b + self.c)
self._a = v
self._reset()
@writeprop('_b')
def b(self, v):
self.check(v, self.a, self.c)
self._b = v
self._reset()
@writeprop('_c')
def c(self, v):
self.check(v, self.a, self.b)
self._c = v
self._reset()
# One kind of triangle with angles:
class Triangle(BaseTriangle):
@property
def perimeter(self):
return self.a + self.b + self.c
@property
def area(self):
"Heron's Formula"
s = 0.5 * self.perimeter
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
@property
def A(self):
return math.acos((-self.a**2 + self.b**2 + self.c**2)
/ (2.0 * self.b * self.c))
@property
def B(self):
return math.acos((self.a**2 - self.b**2 + self.c**2)
/ (2.0 * self.a * self.c))
@property
def C(self):
return math.acos((self.a**2 + self.b**2 - self.c**2)
/ (2.0 * self.a * self.b))
# Another kind of triangle with angles:
class Triangle2(BaseTriangle):
def _reset(self):
self.perimeter = self.a + self.b + self.c
s = 0.5 * self.perimeter
self.area = math.sqrt(s * (s - self.a)
*(s - self.b) * (s - self.c))
self.A = math.acos((-self.a**2 + self.b**2 + self.c**2)
/ (2.0 * self.b * self.c))
self.B = math.acos((self.a**2 - self.b**2 + self.c**2)
/ (2.0 * self.a * self.c))
self.C = math.acos((self.a**2 + self.b**2 - self.c**2)
/ (2.0 * self.a * self.b))
--Scott David Daniels
Scott.Daniels at Acm.Org
More information about the Edu-sig
mailing list