Chapter 4. Functions and Interfaces

This chapter introduces a module called jupyturtle, which allows you to create simple drawings by giving instructions to an imaginary turtle. We will use this module to write functions that draw squares, polygons, and circles—and to demonstrate interface design, which is a way of designing functions that work together.

The jupyturtle Module

To use the jupyturtle module, we can import it like this:

import jupyturtle
       

Now we can use the functions defined in the module, like make_turtle and forward:

jupyturtle.make_turtle()
jupyturtle.forward(100)
       

make_turtle creates a canvas, which is a space on the screen where we can draw, and a turtle, which is represented by a circular shell and a triangular head. The circle shows the location of the turtle and the triangle indicates the direction it is facing.

forward moves the turtle a given distance in the direction it’s facing, drawing a line segment along the way. The distance is in arbitrary units—the actual size depends on your computer’s screen.

We will use functions defined in the jupyturtle module many times, so it would be nice if we did not have to write the name of the module every time. That’s possible if we import the module like this:

from jupyturtle import make_turtle, forward
       

This version of the import statement imports make_turtle and forward from the jupyturtle module so we can call them like this:

make_turtle()
forward(100)
       

jupyturtle provides two other functions we’ll use, called left and right. We’ll import them like this:

from jupyturtle import left, right
       

left causes the turtle to turn left. It takes one argument, which is the angle of the turn in degrees. For example, we can make a 90 degree left turn like this:

make_turtle()
forward(50)
left(90)
forward(50)
       

This program moves the turtle east and then north, leaving two line segments behind. Before you go on, see if you can modify the program to make a square.

Encapsulation and Generalization

Let’s take the square-drawing code from the previous section and put it in a function called square:

def square():
    for i in range(4):
        forward(50)
        left(90)
       

Now we can call the function like this:

make_turtle()
square()
        

Wrapping a piece of code up in a function is called encapsulation. One of the benefits of encapsulation is that it attaches a name to the code, which serves as a kind of documentation. Another advantage is that if you re-use the code, it is more concise to call a function twice than to copy and paste the body!

In the current version, the size of the square is always 50. If we want to draw squares with different sizes, we can take the length of the sides as a parameter:

def square(length):
    for i in range(4):
        forward(length)
        left(90)
        

Now we can draw squares with different sizes:

make_turtle()
square(30)
square(60)
        

Adding a parameter to a function is called generalization because it makes the function more general: with the previous version, the square is always the same size; with this version it can be any size.

If we add another parameter, we can make it even more general. The following function draws regular polygons with a given number of sides:

def polygon(n, length):
    angle = 360 / n
    for i in range(n):
        forward(length)
        left(angle)
        

In a regular polygon with n sides, the angle between adjacent sides is 360 / n degrees.

The following example draws a 7-sided polygon with side length of 30:

make_turtle()
polygon(7, 30)
        

When a function has more than a few numeric arguments, it is easy to forget what they are, or what order they should be in. It can be a good idea to include the names of the parameters in the argument list:

make_turtle()
polygon(n=7, length=30)
        

These are sometimes called “named arguments” because they include the parameter names. But in Python they are more often called keyword arguments (not to be confused with Python keywords like for and def).

This use of the assignment operator, =, is a reminder about how arguments and parameters work—when you call a function, the arguments are assigned to the parameters.

Refactoring

Now let’s write a more general version of circle, called arc, that takes a second parameter, angle, and draws an arc of a circle that spans the given angle. For example, if angle is 360 degrees, it draws a complete circle. If angle is 180 degrees, it draws a half circle.

To write circle, we were able to reuse polygon, because a many-sided polygon is a good approximation of a circle. But we can’t use polygon to write arc.

Instead, we’ll create the more general version of polygon, called polyline:

def polyline(n, length, angle):
    for i in range(n):
        forward(length)
        left(angle)
        

polyline takes as parameters the number of line segments to draw, n; the length of the segments, length; and the angle between them, angle.

Now we can rewrite polygon to use polyline:

def polygon(n, length):
    angle = 360.0 / n
    polyline(n, length, angle)
        

And we can use polyline to write arc:

def arc(radius, angle):
    arc_length = 2 * math.pi * radius * angle / 360
    n = 30
    length = arc_length / n
    step_angle = angle / n
    polyline(n, length, step_angle)
        

arc is similar to circle, except that it computes arc_length, which is a fraction of the circumference of a circle.

Finally, we can rewrite circle to use arc:

def circle(radius):
    arc(radius,  360)
        

To check that these functions work as expected, we’ll use them to draw something like a snail. With delay=0, the turtle runs as fast as possible.

make_turtle(delay=0)
polygon(n=20, length=9)
arc(radius=70, angle=70)
circle(radius=10)
        

In this example, we started with working code and reorganized it with different functions. Changes like this, which improve the code without changing its behavior, are called refactoring.

If we had planned ahead, we might have written polyline first and avoided refactoring, but often you don’t know enough at the beginning of a project to design all the functions. Once you start coding, you understand the problem better. Sometimes refactoring is a sign that you have learned something.

A Development Plan

A development plan is a process for writing programs. The process we used in this chapter is “encapsulation and generalization.” The steps of this process are:

  1. Start by writing a small program with no function definitions.

  2. Once you get the program working, identify a coherent piece of it, encapsulate the piece in a function, and give it a name. Copy and paste working code to avoid retyping (and re-debugging).

  3. Generalize the function by adding appropriate parameters.

  4. Repeat steps 1 through 3 until you have a set of working functions.

  5. Look for opportunities to improve the program by refactoring. For example, if you have similar code in several places, consider factoring it into an appropriately general function.

This process has some drawbacks—we will see alternatives later—but it can be useful if you don’t know ahead of time how to divide the program into functions. This approach lets you design as you go along.

The design of a function has two parts:

interface

How the function is used, including its name, the parameters it takes, and what the function is supposed to do

implementation

How the function does what it’s supposed to do

For example, here’s the first version of circle we wrote, which uses polygon:

def circle(radius):
    circumference = 2 * math.pi * radius
    n = 30
    length = circumference / n
    polygon(n, length)
        

And here’s the refactored version that uses arc:

def circle(radius):
    arc(radius,  360)
        

These two functions have the same interface—they take the same parameters and do the same thing—but they have different implementations.

Exercises

For these exercises, there are a few more turtle functions you might want to use:

penup

Lift the turtle’s imaginary pen so it doesn’t leave a trail when it moves.

pendown

Put the pen back down.

The following function uses penup and pendown to move the turtle without leaving a trail:

from jupyturtle import penup, pendown

def jump(length):
    """Move forward length units without leaving a trail.
            
    Postcondition: Leaves the pen down.
    """
    penup()
    forward(length)
    pendown()
        

Exercise

Write a function called rhombus that draws a rhombus with a given side length and a given interior angle. For example, here’s a rhombus with side length of 50 and an interior angle of 60 degrees:

Exercise

Now write a more general function called parallelogram that draws a quadrilateral with parallel sides. Then rewrite rectangle and rhombus to use parallelogram.

Exercise

Write an appropriately general set of functions that can draw shapes like this.

Hint: write a function called triangle that draws one triangular segment, and then a function called draw_pie that uses triangle.

Exercise

Write an appropriately general set of functions that can draw flowers like this.

Hint: use arc to write a function called petal that draws one flower petal.

Ask a Virtual Assistant

Several modules like jupyturtle in Python, and the one we used in this chapter have been customized for this book. So if you ask a virtual assistant for help, it won’t know which module to use. But if you give it a few examples to work with, it can probably figure it out. For example, try this prompt and see if it can write a function that draws a spiral:

The following program uses a turtle graphics module to draw a circle:

from jupyturtle import make_turtle, forward, left
import math

def polygon(n, length):
    angle = 360 / n
    for i in range(n):
        forward(length)
        left(angle)
        
def circle(radius):
    circumference = 2 * math.pi * radius
    n = 30
    length = circumference / n
    polygon(n, length)
    
make_turtle(delay=0)
circle(30)

Write a function that draws a spiral.

Keep in mind that the result might use features we have not seen yet, and it might have errors. Copy the code from the virtual assistant and see if you can get it working. If you didn’t get what you wanted, try modifying the prompt.