Immutable Geometry Types
Gabriel Genellina
gagsl-py2 at yahoo.com.ar
Sun Dec 16 23:46:44 EST 2007
En Sun, 16 Dec 2007 23:13:47 -0300, PeterBraden1 at googlemail.com
<PeterBraden1 at googlemail.com> escribi�:
> Hi,
>
> I am learning python, having learnt most of my object orientation with
> java, and decided to port some of my geometry classes over. I haven't
> used immutability in python before, so thought this would be an
> interesting chance to learn.
>
> I am looking for feedback on any ways in which I might have left
> variables unprotected, and ways in which I am not being pythonic.
Immutable classes usually implement __new__ and leave __init__
unimplemented:
def __new__(cls, val, measure='rad'):
# using 'type' as a name shadows the builtin type
instance = super(Angle, self).__new__()
if measure == 'rad': pass
elif measure == 'deg': value = math.radians(value)
else: raise ValueError, "unknown Angle measure: %r" % measure
instance._value = value
return instance
For the various comparison operators, since this class "plays well" with
ordering, it's easier to implement __cmp__ and make all operators refer to
it:
def __cmp__(self, other):
if isinstance(other, Angle):
return cmp(self._value, other._value)
raise NotImplementedError
__eq__ = lambda self,other: self.__cmp__(other)==0
__ne__ = lambda self,other: self.__cmp__(other)!=0
__lt__ = lambda self,other: self.__cmp__(other)<0
__ge__ = lambda self,other: self.__cmp__(other)>=0
__gt__ = lambda self,other: self.__cmp__(other)>0
__le__ = lambda self,other: self.__cmp__(other)<=0
> def __setattr__(self, name, value):
> """
> Suppress setting of data - Angle is immutable
> """
> self._immutableError()
Why? This class contains a single attribute, value, and we could make it
immutable. But why restrict the possibility to add *other* attributes?
(There is __slots__ too, but I would not recommend it at this stage)
> def __delattr__(self, name):
Same as above.
> def __mul__(self, other):
> def __div__(self, other):
(I've never seen those operations on angles)
> def fromCos(self, c):
> """
> return angle with specified cos
> """
> return Angle(math.acos(c))
>
> fromCos = classmethod(fromCos)
I prefer to write
@classmethod
def fromCos(self, c):
> radians = property(lambda self: self._value, lambda x:
> self._immutableError())
> degrees = property(lambda self: math.degrees(self._value), lambda
> x: self._immutableError())
> cos = property(lambda self: math.cos(self._value), lambda x:
> self._immutableError())
> sin = property(lambda self: math.sin(self._value), lambda x:
> self._immutableError())
> tan = property(lambda self: math.tan(self._value), lambda x:
> self._immutableError())
You don't have to write them that way. Just omit the property setter, and
it will be read-only.
tan = property(lambda self: math.tan(self._value))
Or:
@property
def tan(self):
return math.tan(self.value)
> def withinRange(self, angle, range):
> """
> angle is within range of self
> """
> return (self._value < angle._value + range) and (self._value >
> angle._value - range)
I think this is more readable:
return angle.value-range < self.value < angle.value+range
> class Point2D (object):
Same as above, replace __init__ by __new__. You may inherit from tuple
instead, and get some basic methods already implemented.
> length = property(lambda self: return math.sqrt(self.x*self.x +
> self.y*self.y), lambda x: self._immutableError())
Using math.hypot is safer in some circunstances.
> def normalise(self):
> return Point2D(self.x/self.length, self.y/self.length);
> def translate(self, other) {
> return self + other
> }
I'd use "normalised", "translated", etc. because they return a *new*
object, instead of modifying self.
(You forgot to remove some braces, I presume...)
--
Gabriel Genellina
More information about the Python-list
mailing list