[Edu-sig] Design patterns

Kirby Urner urnerk at qwest.net
Sun Aug 21 18:14:32 CEST 2005

Hi Arthur --

Yes, decorator syntax isn't designed to integrate with properties quite the
same way.  

All a decorator does is run the subsequently defined function f2 through the
f1 in @f1, rebinding f2 to the returned output of f1.  E.g. 'f2 = f1(f2)'
and '@f1; def f2(): pass' amount to the same thing.

The non-decorator version of Triangle, with properties, might be:

>>> import math

>>> class Triangle(object):

     def __init__(self, a,b,c):
         "Construct a triangle"
         if (c >= a + b) or (a >= b + c) or (b >= a + c):
             raise ValueError("Illegal edges: %s/%s/%s" % (a, b, c))
         self.a = a
         self.b = b
         self.c = c

     def getArea(self):
         "Heron's Formula"
         s = 0.5 * (self.a + self.b + self.c)
         return math.sqrt( s * (s - self.a) *
                         (s - self.b) * (s - self.c))

     def getA(self):
	return math.acos((-self.a**2 + self.b**2 + self.c**2)
			 / (2.0 * self.b * self.c))

     def getB(self):
	return math.acos((self.a**2 - self.b**2 + self.c**2)
			 / (2.0 * self.a * self.c))

     def getC(self):
	return math.acos((self.a**2 + self.b**2 - self.c**2)
			 / (2.0 * self.a * self.b))

     A = property(getA, doc='Angle A in radians')
     B = property(getB, doc='Angle B in radians')
     C = property(getC, doc='Angle C in radians')
     area = property(getArea, doc="Triangle's area")
>>> mytri = Triangle(3,4,5)
>>> math.degrees(mytri.C)
>>> math.degrees(mytri.A)
>>> mytri.area = 6

Traceback (most recent call last):
  File "<pyshell#28>", line 1, in -toplevel-
    mytri.area = 6
AttributeError: can't set attribute

Alex Martelli's example in 'Python in a Nutshell' is similar:  a rectangle's
area is computed on the fly (pg. 85).

Note that defining attributes in this way doesn't mean we only find out an
attribute is really a property much further along in the code (everything is
expressed in one line) -- which was the problem decorators addressed i.e. a
long function def might obscure the fact that we're defining a classmethod
(the older f1 = classmethod(f1) had to come at the end).

>>> help(Triangle) # now get us this useful section in the documentation:

 |  ----------------------------------------------------------------------
 |  Properties defined here:
 |  A
 |      Angle A in radians
 |      <get> = getA(self)
 |  B
 |      Angle B in radians
 |      <get> = getB(self)
 |  C
 |      Angle C in radians
 |      <get> = getC(self)
 |  area
 |      Triangle's area
 |      <get> = getArea(self)
 |          Heron's Formula
 |  ----------------------------------------------------------------------

I think it's fine to call it "syntactic sugar" when we make object
attributes call their associated accessors and mutators behind the scenes.

Conceptually, we're giving the user of the class a simpler API while giving
ourselves as programmers a lot of control over what triggers what.

It's easy to think of angles and area as attributes, even if we don't allow
them to be set except through changes to edges.  Why should we force a user
to remember what's a method and what's not, given edges, angles and area
(could add perimeter too).

A weakness in the above design:  we only check for violations of triangle
inequality in the constructor, yet allow changes to a,b,c through the API.


More information about the Edu-sig mailing list