Classes

Point class

We will write a class for a point in a two dimensional Euclidian space ($\mathbb{R}^2$).

We start with the class definition (def) and the constructor (__init__) which defines the creation of a new class instance.

Note:

  • The first argument to class methods (class functions) is always self, a reference to the instance.
  • The other arguments to __init__ have a default values 0.
  • We assert that the __init__ arguments are numbers.

In [1]:
class Point():
    """Holds on a point (x,y) in the plane"""
    
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)

In [2]:
p = Point(1,2)
print("point", p.x, p.y)
origin = Point()
print("origin", origin.x, origin.y)


point 1.0 2.0
origin 0.0 0.0

Notice that when we send a Point to the console we get:


In [3]:
p


Out[3]:
<__main__.Point at 0x22011d27898>

Which is not useful, so we will define how Point is represented in the console using __repr__.


In [4]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"

In [5]:
Point(1,2)


Out[5]:
Point(1.0, 2.0)

Next up we define a method to add two points. Addition is by elements - $(x_1, y_1) + (x_2, y_2) = (x_1+x_2, y_1+y_2)$.

We also allow to add an int, in which case we add the point to a another point with both coordinates equal to the argument value.


In [6]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"
    
    def add(self, other):
        assert isinstance(other, (int, Point))
        if isinstance(other, Point):
            return Point(self.x + other.x , self.y + other.y)
        else: # other is int, taken as (int, int)
            return Point(self.x + other , self.y + other)

In [7]:
Point(1,1).add(Point(2,2))


Out[7]:
Point(3.0, 3.0)

In [8]:
Point(1,1).add(2)


Out[8]:
Point(3.0, 3.0)

A nicer way to do it is to overload the addition operator + by defining the addition method name to a name Python reserves for addition - __add__ (those are double underscores):


In [9]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"
    
    def __add__(self, other):
        assert isinstance(other, (int, Point))
        if isinstance(other, Point):
            return Point(self.x + other.x , self.y + other.y)
        else: # other is int, taken as (int, int)
            return Point(self.x + other , self.y + other)

In [10]:
Point(1,1) + Point(2,2)


Out[10]:
Point(3.0, 3.0)

In [11]:
Point(1,1) + 2


Out[11]:
Point(3.0, 3.0)

We want to be a able to compare Points:


In [12]:
Point(1,2) == Point(2,1)


Out[12]:
False

In [13]:
Point(1,2) == Point(1,2)


Out[13]:
False

In [14]:
p = Point()
p == p


Out[14]:
True

In [15]:
Point(1,2) > Point(2,1)


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-fa6a973b1233> in <module>()
----> 1 Point(1,2) > Point(2,1)

TypeError: unorderable types: Point() > Point()

So == checks by identity and > is not defined. Let us overload both these operators:


In [16]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"
    def __add__(self, other):
        assert isinstance(other, (int, Point))
        if isinstance(other, Point):
            return Point(self.x + other.x , self.y + other.y)
        else: # other is int, taken as (int, int)
            return Point(self.x + other , self.y + other)
    
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    
    def __gt__(self, other):
        return (self.x > other.x and self.y > other.y)

First we check if two points are equal:


In [17]:
Point(1,0) == Point(1,2)


Out[17]:
False

In [18]:
Point(1,0) == Point(1,0)


Out[18]:
True

Then if one is strictly smaller than the other:


In [19]:
Point(1,0) > Point(1,2)


Out[19]:
False

The addition operator + returns a new instance.

Next we will write a method that instead of returning a new instance, changes the current instance:


In [36]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    def __gt__(self, other):
        return (self.x > other.x and self.y > other.y)
    def __add__(self, other):
        assert isinstance(other, (int, Point))
        if isinstance(other, Point):
            return Point(self.x + other.x , self.y + other.y)
        else: # other is int, taken as (int, int)
            return Point(self.x + other , self.y + other)
    
    def increment(self, other): 
        '''this method changes self (add "inplace")'''
        assert isinstance(other,Point)
        self.x += other.x
        self.y += other.y

In [37]:
p = Point(6.5, 7)
p + Point(1,2)
print(p)
p.increment(Point(1,2))
print(p)


Point(6.5, 7.0)
Point(7.5, 9.0)

In [20]:
Point(5,6) > Point(1,2)


Out[20]:
True

We now write a method that given many points, checks if the current point is more extreme than the other points.

Note that the argument *points means that more than one argument may be given.


In [21]:
class Point():
    """Holds on a point (x,y) in the plane"""
    def __init__(self, x=0, y=0):
        assert isinstance(x, (int, float)) and isinstance(y, (int, float))
        self.x = float(x)
        self.y = float(y)
    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    def __lt__(self, other):
        return (self.x < other.x and self.y < other.y)
    def __add__(self, other):
        assert isinstance(other, (int, Point))
        if isinstance(other, Point):
            return Point(self.x + other.x , self.y + other.y)
        else: # other is int, taken as (int, int)
            return Point(self.x + other , self.y + other)
    def increment(self, other): 
        '''this method changes self (add "inplace")'''
        assert isinstance(other,Point)
        self.x += other.x
        self.y += other.y
    
    def is_extreme(self, *points):
        for point in points:
            if not self > point:
                return False
        return True

In [22]:
p = Point(5, 6)
p.is_extreme(Point(1,1))


Out[22]:
True

In [23]:
p.is_extreme(Point(1,1), Point(2,5), Point(6,2))


Out[23]:
False

We can also use the method via the class instead of the instance, and give the instance of interest (the one that we want to know if it is the extreme) as the first argument self. Much like this, we can either do 'hi'.upper() or str.upper('hi').


In [24]:
Point.is_extreme(Point(7,8), Point(1,1), Point(4,5), Point(2,3))


Out[24]:
True

Rectangle class

We will implement two classes for rectangles, and compare the two implementations.

First implementation - two points

The first implementation defines a rectangle by its lower left and upper right vertices.


In [25]:
class Rectangle1():
    """
    Holds a parallel-axes rectangle by storing two points
    lower left vertex - llv
    upper right vertex - urv
    """
    def __init__(self, lower_left_vertex, upper_right_vertex):
        assert isinstance(lower_left_vertex, Point)
        assert isinstance(upper_right_vertex, Point)
        assert lower_left_vertex < upper_right_vertex 
        self.llv = lower_left_vertex
        self.urv = upper_right_vertex
        
    def __repr__(self):
        representation = "Rectangle with lower left {0} and upper right {1}"
        return representation.format(self.llv, self.urv)

    def dimensions(self):
        height = self.urv.y - self.llv.y
        width = self.urv.x - self.llv.x
        return height, width
    
    def area(self):
        height, width = self.dimensions()
        area = height * width
        return area
    
    def transpose(self):
        """
        Reflection with regard to the line passing through lower left vertex with angle 315 (-45) degrees
        """
        height, width = self.dimensions()
        self.urv = self.llv
        self.llv = Point(self.urv.x - height, self.urv.y - width)

In [26]:
rec = Rectangle1(Point(), Point(2,1))
print(rec)
print("Area:", rec.area())
print("Dimensions:", rec.dimensions())
rec.transpose()
print("Transposed:", rec)


Rectangle with lower left Point(0.0, 0.0) and upper right Point(2.0, 1.0)
Area: 2.0
Dimensions: (1.0, 2.0)
Transposed: Rectangle with lower left Point(-1.0, -2.0) and upper right Point(0.0, 0.0)

Second implementation - point and dimensions

The second implementation defines a rectangle by the lower left point, the height and the width.

We define the exact same methods as in Rectangle1, with the same input and output, but different inner representation / implementation.


In [27]:
class Rectangle2():
    """
    Holds a parallel-axes rectangle by storing lower left point, height and width
    """
    def __init__(self, point, height, width):
        assert isinstance(point, Point)
        assert isinstance(height, (int,float))
        assert isinstance(width, (int,float))
        assert height > 0
        assert width > 0        
        self.point = point
        self.height = float(height)
        self.width = float(width)
        
    def __repr__(self):
        representation = "Rectangle with lower left {0} and upper right {1}"
        return representation.format(self.point, Point(self.point.x + self.width, self.point.y + self.height))
    
    def dimensions(self):
        return self.height, self.width

    def area(self):
        area = self.height * self.width
        return area

    def transpose(self):
        self.point = Point(self.point.x - self.height , self.point.y - self.width)
        self.height, self.width = self.width, self.height

In [29]:
rec = Rectangle2(Point(), 1, 2)
print(rec)
print("Area:", rec.area())
print("Dimensions:", rec.dimensions())
rec.transpose()
print("Transposed:", rec)


Rectangle with lower left Point(0.0, 0.0) and upper right Point(2.0, 1.0)
Area: 2.0
Dimensions: (1.0, 2.0)
Transposed: Rectangle with lower left Point(-1.0, -2.0) and upper right Point(0.0, 0.0)

In [ ]: