Comp 112

Lecture 14

Classes and Objects

2017.12.05

Drawing a dot

Suppose that we want turtle to draw a dot at a given point.

import turtle
turtle.tracer(0,0)    #  don't render to screen unless instructed 
turtle.hideturtle ()

dot_size = 10

def dot_at (x , y) :
    turtle.clear ()
    turtle.penup ()
    turtle.goto (x , y)
    turtle.dot (dot_size)
    turtle.update ()    #  render to the screen

Drawing a dot at a given point

We can give the dot an initial location:

dot_x  =  0
dot_y  =  0

And write a function to draw the dot at the given location:

def draw_dot () :
    dot_at (dot_x , dot_y)

Moving the dot: first attempt

Now let’s try to add an event handler to try to move the dot.

def move_right () :
    dot_x += 10

def right_event () :
    move_right ()
    draw_dot ()

turtle.onkeypress (right_event , 'Right')
turtle.listen ()  #  activates event handlers

This won’t work because dot_x is a global variable, so we can’t reassign it from within a function (without some trickery).

Classes as dictionaries

One solution is to introduce a new type, known as a class.

class Point :
    pass  #  our new class has no attributes yet

We can make a new value, called an object, of this type like this:

my_point = Point ()

An object acts as a dictionary, or variable table where we can store information:

# initialize our point:
my_point.x = 0
my_point.y = 0

Like dictionaries and variable tables, objects are mutable: their state can change over time.

Writing an interface for Points:

Instead of manipulating the state of a Point directly, we can write an interface for it:

def set_x (point , x_coord) :
    # signature: Point , int -> NoneType
    point.x = x_coord

def set_y (point , y_coord) :
    # signature: Point , int -> NoneType
    point.y = y_coord

def get_x (point) :
    # signature: Point -> int
    return point.x

def get_y (point) :
    # signature: Point -> int
    return point.y

def move_right (point) :
    # signature: Point -> NoneType
    point.x += 10

Interfaces are useful so that the user doesn’t need to care about how an object’s state is represented internally.

Moving the dot: second attempt

Now we can use the Point object, my_point to fix our event handlers for moving the dot:

def draw_dot (point) :
    # signature:  Point -> NoneType
    dot_at (get_x (point) , get_y (point))

def right_event () :
    move_right (my_point)
    draw_dot (my_point)

turtle.onkeypress (right_event , 'Right')
turtle.listen ()  #  activates event handlers 

Moving the Interface into the Class

Recall that we can use the convenient method syntax for functions that act on many types.

Unfortunately, this will not work for the Point type (yet).

We can fix this by making the interface functions part of the class:

class Point :
    
    def set_x (point , x_coord) :
        point.x = x_coord

    def set_y (point , y_coord) :
        point.y = y_coord

    def get_x (point) :
        return point.x

    def get_y (point) :
        return point.y

    def move_right (point) :
        point.x += 10

Then we can call them in either function or method form:

my_point = Point ()
Point.set_x (my_point , 0)  #  ordinary function syntax
my_point.set_y (0)          #  method syntax

Initializing the state of new objects

It’s annoying that before we can use a Point object, we have to manually initialize it by calling set_x and set_y.

We can make this initialization step automatic be defining a constructor method.

A constructor is a method with the special name “__init__” (underscore, underscore, “init”, underscore, underscore).

The constructor automatically gets called whenever you create a new object of the class.

class Point :
    
    def __init__ (point , init_x , init_y) :
        point.x = init_x
        point.y = init_y
    
    def set_x (point , x_coord) :
        point.x = x_coord

    def set_y (point , y_coord) :
        point.y = y_coord

    def get_x (point) :
        return point.x

    def get_y (point) :
        return point.y

    def move_right (point) :
        point.x += 10

Putting it all together

We can streamline our dot-moving program now:

# use the constructor to create a new Point object:
my_point = Point (0 , 0)

def draw_dot (point) :
    # use interface methods to access the point's state:
    dot_at (point.get_x () , point.get_y ())

def right_event () :
    # use an interface method to update the point's state:
    my_point.move_right ()
    draw_dot (my_point)

# set up an event listener:
turtle.onkeypress (right_event , 'Right')
turtle.listen ()  #  activates event handlers

Exercises:

To Do This Week: