Immutable Geometry Types

PeterBraden1 at googlemail.com PeterBraden1 at googlemail.com
Sun Dec 16 21:13:47 EST 2007


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.

Cheers!

<code class = "python">
#!/usr/bin/env python
#
#   geometry.py
#
#   Peter Braden <http://PeterBraden.co.uk>
#
#   Released under the GPLv2 (http://www.gnu.org/licenses/
gpl-2.0.txt).
#
#   Disclaimer:
#
#  All following code is provided "as is". Peter Braden hereby
disclaims
#  solely to the extent permitted by law all express, implied and
statutory
#  warranties, conditions and terms including, without limitation,
those
#  regarding the security, reliability, timeliness, and performance of
the code.
#
#

import math

class Angle (object):
    """
    The Angle class represents angles; the inclination to each other,
in a plane,
    of two lines which meet each other, and do not lie straight with
respect to
    each other.

    * By default angles are measured in radians.

    * Angles are immutable
   """



    def __init__(self, val, type = 'rad'):
        """
        Create a new angle.

        * To specify type of value use either type = "deg" or type
="rad"
        * default is radians
        """
        if type == 'rad':
            super(Angle, self).__setattr__('_value', val)
        else:
            super(Angle, self).__setattr__('_value',
math.radians(val))


    def __eq__(self, other):
        """
        Test equality
        """
        if isinstance(other, Angle):
            return self._value == other._value
        return NotImplemented

    def __ne__(self, other):
        """
        Test Inequality
        """
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    def __str__(self):
        """
        Create Readable String
        """
        return "%s (%s degrees)" % (str(self._value),
str(self.degrees))

    def __repr__(self):
        """
        Serialise data
        """
        return "Angle(%s)" % self._value

    def __setattr__(self, name, value):
        """
        Suppress setting of data - Angle is immutable
        """
        self._immutableError()

    def __delattr__(self, name):
        """
        Suppress deleting of data - Angle is immutable
        """
        self._immutableError()

    def __add__(self, other):
        """
        return self + other
        """
        if isinstance(other, Angle):
            return Angle(self._value + other._value)
        return NotImplemented

    def __sub__(self, other):
        """
        return self - other
        """
        if isinstance(other, Angle):
            return Angle(self._value - other._value)
        return NotImplemented

    def __mul__(self, other):
        """
        return self * other
        """
        if isinstance(other, Angle):
            return Angle(self._value * other._value)
        return NotImplemented

    def __div__(self, other):
        """
        return self / other
        """
        if isinstance(other, Angle):
            return Angle(self._value / other._value)
        return NotImplemented

    def __lt__(self, other):
        """
        return self < other
        """
        if isinstance(other, Angle):
            return self._value < other._value
        return NotImplemented

    def __gt__(self, other):
        """
        return self > other
        """
        if isinstance(other, Angle):
            return self._value > other._value
        return NotImplemented

    def __le__(self, other):
        """
        return self >= other
        """
        if isinstance(other, Angle):
            return self._value <= other._value
        return NotImplemented

    def __ge__(self, other):
        """
        return self <= other
        """
        if isinstance(other, Angle):
            return self._value >= other._value
        return NotImplemented


    def fromCos(self, c):
        """
        return angle with specified cos
        """
        return Angle(math.acos(c))

    fromCos = classmethod(fromCos)

    def fromSin(self, s):
        """
        return angle with specified sin
        """
        return Angle(math.asin(c))

    fromSin = classmethod(fromSin)

    def fromTan(self, t):
        """
        return angle with specified tan
        """
        return Angle(math.atan(c))

    fromTan = classmethod(fromTan)


    def _immutableError(self):
        """
        Throw error about angles immutability
        """
        raise TypeError("Angle is immutable - cannot alter variables")

    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())

    def withinRange(self, angle, range):
        """
        angle is within range of self
        """
        return (self._value < angle._value + range) and (self._value >
angle._value - range)

    def isAcute(self):
        """
        angle is acute?
        """
        return self._value < (math.pi/2)


#Common Values

DEG_30 = Angle(math.radians(30))
DEG_60 = Angle(math.radians(60))
DEG_90 = Angle(math.radians(90))
DEG_120 = Angle(math.radians(120))
DEG_180 = Angle(math.radians(180))
DEG_270 = Angle(math.radians(270))
DEG_360 = Angle(math.radians(360))


class Point2D (object):
    """
 2 dimensional point type.

 * Can represent both vectors and points

 * Immutable
    """

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if isinstance(other, Point2D):
            return self.x == other.x and self.y == other.y
        return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    def __str__(self):
        return "<%s, %s>" % (str(self.x),  str(self.y))

    def __repr__(self):
        return "Point2D(%s, %s)" % (self.x, self.y)

    def __setattr__(self, name, value):
        self._immutableError()

    def __delattr__(self, name):
        self._immutableError()

    def __add__(self, other):
        if isinstance(other, Point2D):
            return Point2D(self.x + other.x, self.y +other.y)
        return NotImplemented

    def __sub__(self, other):
        if isinstance(other, Point2D):
            return Point2D(self.x - other.x, self.y - other.y)
        return NotImplemented

    def __mul__(self, other):
        if isinstance(other, Point2D):
            return self.x*other.x + self.y*other.y)
        return NotImplemented

    def __getitem__(self,index):
        if index == 0:
            return self.x
        if index == 1:
            return self.y
        raise TypeError("Index out of bounds for Point2D (x is 0, y is
1)")

    def __setitem__(self,index,value):
        self._immutableError()

    def __delitem__(self,index,value):
        self._immutableError()



    def _immutableError(self):
        raise TypeError("Point2D is immutable - cannot alter
variables")

    length = property(lambda self: return math.sqrt(self.x*self.x +
self.y*self.y), lambda x: self._immutableError())

    def normalise(self):
        return Point2D(self.x/self.length, self.y/self.length);

    def translate(self, other) {
        return self + other
    }

    def rotate(self, pivot, rot) {
        return Point2D( ((self.x - pivot.x) * rot.cos - (self.y -
pivot.y)* rot.sin) + pivot.x,
                ((self.x - pivot.x)*rot.sin + (self.y -
pivot.y)*rot.cos)+pivot.y)
    }


    def dotProduct(self, c){
        return self * c
    }

    def crossProductLength(self, p){
        return (self.x * p.y - self.y * p.x);
    }
</code>



More information about the Python-list mailing list