Programmer-defined types

We have learned a lot of Python's built-in types. Now let's define a new type: Point, to represent a point in 2-D space.

There are several ways we might represent points in Python:

  • We could store the coordinates separately in two variables, x and y.
  • We could store the coordinates as elements in a list or tuple.
  • We could create a new type to represent points as objects.

Creating a new type is more complicated than the other options, but it has advantages that will be apparent soon.

A programmer-defined type is also called a class. A class definition looks like this:

class Point:
    """Represents a point in 2-D space."""

The header indicates that the new class is called Point. The body is a docstring that explains what the class is for. You can define variables and methods inside a class definition, but we will get back to that later.

The class object is like a factory for creating objects. To create a Point, you call Point as if it were a function.

my_point = Point()
my_point
<__main__.Point at 0x1b4e7fbf208>

The return value is a reference to a Point object, which we assign to my_point.

Creating a new object is called instantiation, and the object is an instance of the class.

print(type(my_point))
print(isinstance(my_point, Point))
<class '__main__.Point'>
True

Attributes

my_point.x = 3
my_point.y = 4

The variable my_point refers to a Point object, which contains two attributes. Each attribute refers to a floating-point number.

print(my_point.x)
print(my_point.y)
x = my_point.y
print(x)
print(my_point.x)
3
4
4
3

The expression my_point.y means, “Go to the object my_point refers to and get the value of y.” In the example, we assign that value to a variable named x. There is no conflict between the variable x and the attribute x.

You can use dot notation as part of any expression. For example:

import math

print(f'({my_point.x}, {my_point.y})')
distance = math.sqrt(my_point.x**2 + my_point.y**2)
print(distance)
(3, 4)
5.0

You can pass an instance as an argument in the usual way. For example:

def print_point(p):
    print(f'({p.x}, {p.y})')
    
print_point(my_point)
(3, 4)

Inside the function, p is an alias for my_point, so if the function modifies p, my_point changes.

If you are not sure whether an object has a particular attribute, you can use the built-in function hasattr:

print(hasattr(my_point, 'x'))
print(hasattr(my_point, 'z'))
True
False

Exercise 01

Write a function called distance_between_points that takes two Points as arguments and returns the distance between them.

Rectangular

Imagine you are designing a class to represent rectangles. What attributes would you use to specify the location and size of a rectangle? You can ignore angle; to keep things simple, assume that the rectangle is either vertical or horizontal.

class Rectangle:
    """Represents a rectangle. 

    attributes: width, height, corner.
    """

The docstring lists the attributes: width and height are numbers; corner is a Point object that specifies the lower-left corner.

box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point() 
box.corner.x = 0.0
box.corner.y = 0.0

An object that is an attribute of another object is embedded.

Instances as return values

def find_center(rect):
    p = Point()
    p.x = rect.corner.x + rect.width/2
    p.y = rect.corner.y + rect.height/2
    return p

center = find_center(box)
print_point(center)
(50.0, 100.0)

Objects are mutable

box.width = box.width + 50
box.height = box.height + 100

You can also write functions that modify objects.

def grow_rectangle(rect, dwidth, dheight):
    rect.width += dwidth
    rect.height += dheight
    
print(box.width)
print(box.height)
print('grow')
grow_rectangle(box, 50, 100)
print(box.width)
print(box.height)
150.0
300.0
grow
200.0
400.0

Copying

p1 = Point()
p1.x = 3.0
p1.y = 4.0

import copy
p2 = copy.copy(p1)

print_point(p1)
print_point(p2)
(3.0, 4.0)
(3.0, 4.0)

p1 and p2 contain the same data, but they are not the same Point.

print(p1 is p2)
print(p1 == p2)
False
False

Because for programmer-defined types, Python doesn’t know what should be considered equivalent. At least, not yet.

Exercise 02

Write a definition for a class named Circle with attributes center and radius, where center is a Point object and radius is a number.

Instantiate a Circle object that represents a circle with its center at (150, 100) and radius 75.

Write a function named point_in_circle that takes a Circle and a Point and returns True if the Point lies in or on the boundary of the circle.

Write a function named rect_in_circle that takes a Circle and a Rectangle and returns True if the Rectangle lies entirely in or on the boundary of the circle.

Write a function named rect_circle_overlap that takes a Circle and a Rectangle and returns True if any of the corners of the Rectangle fall inside the circle. Or as a more challenging version, return True if any part of the Rectangle falls inside the circle.