5.1 Return values
Some of the built-in functions we have used, such as the math functions, have produced results. Calling the function generates a new value, which we usually assign to a variable or use as part of an expression.
e = math.exp(1.0)
height = radius * math.sin(angle)
But so far, none of the functions we have written has returned a value.
In this chapter, we are going to write functions that return values, which we will call fruitful functions, for want of a better name. The first example is area, which returns the area of a circle with the given radius:
import math
def area(radius):
temp = math.pi * radius**2
return temp
We have seen the return statement before, but in a fruitful function the returnstatement includes a return value. This statement means: "Return immediately from this function and use the following expression as a return value." The expression provided can be arbitrarily complicated, so we could have written this function more concisely:
def area(radius):
return math.pi * radius**2
On the other hand, temporary variables like temp often make debugging easier.
Sometimes it is useful to have multiple return statements, one in each branch of a conditional:
def absoluteValue(x):
if x < 0:
return -x
else:
return x
Since these return statements are in an alternative conditional, only one will be executed. As soon as one is executed, the function terminates without executing any subsequent statements.
Code that appears after a return statement, or any other place the flow of execution can never reach, is called dead code.
In a fruitful function, it is a good idea to ensure that every possible path through the program hits a return statement. For example:
def absoluteValue(x):
if x < 0:
return -x
elif x > 0:
return x
This program is not correct because if x happens to be 0, neither condition is true, and the function ends without hitting a return statement. In this case, the return value is a special value called None:
>>> print (absoluteValue(0))
None
Try This:
As an exercise, write a compare function that returns 1 if x > y, 0 if x == y, and
-1 if x < y.
Solution
5.2 Program development
At this point, you should be able to look at complete functions and tell what they do. Also, if you have been doing the exercises, you have written some small functions. As you write larger functions, you might start to have more difficulty, especially with runtime and semantic errors.
To deal with increasingly complex programs, we are going to suggest a technique called incremental development. The goal of incremental development is to avoid long debugging sessions by adding and testing only a small amount of code at a time.
As an example, suppose you want to find the distance between two points, given by the coordinates (x1, y1) and (x2, y2). By the Pythagorean theorem, the distance, d, is:
The first step is to consider what a distance function should look like in Python. In other words, what are the inputs (parameters) and what is the output (return value)?
In this case, the two points are the inputs, which we can represent using four parameters. The return value is the distance, which is a floating-point value.
Already we can write an outline of the function:
def distance(x1, y1, x2, y2):
return 0.0
Obviously, this version of the function doesn't compute distances; it always returns zero. But it is syntactically correct, and it will run, which means that we can test it before we make it more complicated.
To test the new function, we call it with sample values:
>>> distance(1, 2, 4, 6)
0.0
We chose these values so that the horizontal distance equals 3 and the vertical distance equals 4; that way, the result is 5 (the hypotenuse of a 3-4-5 triangle). When testing a function, it is useful to know the right answer.
At this point we have confirmed that the function is syntactically correct, and we can start adding lines of code. After each incremental change, we test the function again. If an error occurs at any point, we know where it must be in the last line we added.
A logical first step in the computation is to find the differences x2-x1 and
y2-y1. We will store those values in temporary variables named dx and dy and print them.
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print ("dx is", dx)
print ("dy is", dy)
return 0.0
If the function is working, the outputs should be 3 and 4. If so, we know that the function is getting the right arguments and performing the first computation correctly. If not, there are only a few lines to check.
Next we compute the sum of squares of dx and dy:
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
print ("dsquared is: ", dsquared)
return 0.0
Notice that we removed the print statements we wrote in the previous step. Code like that is called scaffolding because it is helpful for building the program but is not part of the final product.
Again, we would run the program at this stage and check the output (which should be 25).
Finally, if we have imported the math module, we can use the sqrt function to compute and return the result:
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsquared)
return result
If that works correctly, you are done. Otherwise, you might want to print the value of result before the return statement.
When you start out, you should add only a line or two of code at a time. As you gain more experience, you might find yourself writing and debugging bigger chunks. Either way, the incremental development process can save you a lot of debugging time.
The key aspects of the process are:
Start with a working program and make small incremental changes. At any point, if there is an error, you will know exactly where it is.
Use temporary variables to hold intermediate values so you can output and check them.
Once the program is working, you might want to remove some of the scaffolding or consolidate multiple statements into compound expressions, but only if it does not make the program difficult to read.
Try This:
Use incremental development to write a function called hypotenuse that
returns the length of the hypotenuse of a right triangle given the lengths of the
two legs as arguments. Record each stage of the incremental development
process as you go.
5.3 Composition
As you should expect by now, you can call one function from within another. This ability is called composition.
As an example, we'll write a function that takes two points, the center of the circle and a point on the perimeter, and computes the area of the circle.
Assume that the center point is stored in the variables xc and yc, and the perimeter point is in xp and yp. The first step is to find the radius of the circle, which is the distance between the two points. Fortunately, there is a function, distance, that does that:
radius = distance(xc, yc, xp, yp)
The second step is to find the area of a circle with that radius and return it:
result = area(radius)
return result
Wrapping that up in a function, we get:
def area2(xc, yc, xp, yp):
radius = distance(xc, yc, xp, yp)
result = area(radius)
return result
We called this function area2 to distinguish it from the area function defined earlier. There can only be one function with a given name within a given module.
The temporary variables radius and result are useful for development and debugging, but once the program is working, we can make it more concise by composing the function calls:
def area2(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))
Try This:
Write a function slope(x1, y1, x2, y2)that returns the slope of the line
through the points (x1, y1) and (x2, y2). Then use this function in a function
called intercept(x1, y1, x2, y2) that returns the y-intercept of the line
through the points (x1, y1) and (x2, y2).
5.4 Boolean functions
Functions can return boolean values, which is often convenient for hiding complicated tests inside functions. For example:
def isDivisible(x, y):
if x % y == 0:
return True
else:
return False
The name of this function is isDivisible. It is common to give boolean functions names that sound like yes/no questions. isDivisible returns either True or False to indicate whether the x is or is not divisible by y.
We can make the function more concise by taking advantage of the fact that the condition of the if statement is itself a boolean expression. We can return it directly, avoiding the if statement altogether:
def isDivisible(x, y):
return x % y == 0
This session shows the new function in action:
>>> isDivisible(6, 4)
False
>>> isDivisible(6, 3)
True
Boolean functions are often used in conditional statements:
if isDivisible(x, y):
print ("x is divisible by y")
else:
print ("x is not divisible by y")
It might be tempting to write something like:
if isDivisible(x, y) == True:
But the extra comparison is unnecessary.
Try This:
Write a function isBetween(x, y, z) that returns True if y<=x<=z or
False otherwise.
5.9 Glossary
fruitful function
A function that yields a return value.
return value
The value provided as the result of a function call.
temporary variable
A variable used to store an intermediate value in a complex calculation.
dead code
Part of a program that can never be executed, often because it appears after a
return statement.
None
A special Python value returned by functions that have no return statement, or a
return statement without an argument.
incremental development
A program development plan intended to avoid debugging by adding and testing
only a small amount of code at a time.
scaffolding
Code that is used during program development but is not part of the final version.
guardian
A condition that checks for and handles circumstances that might cause an error.