Conditionals Top Python Programs and Using Files Contents

Functions

Recall from the chapter on assignment the series of expressions we evaluated to find the y-value of a point on the line:

    y = 5x - 3

First, we assigned values to the slope, the x-value, and the y-intercept:

    >>> m = 5
    >>> x = 9  
    >>> b = -3

Once those variables have been assigned, we can compute the value of y:

    >>> y = m * x + b

    >>> y
    42

Now, suppose we wished to find the y-value corresponding to a different x-value or, worse yet, for a different x-value on a different line. All the work we did would have to be repeated. A function is a way to encapsulate all these operations so we can repeat them with a minimum of effort.

Encapsulating a series of operations

First, we will define a not-too-useful function that calculates y give a slope of 5, a y-intercept of -3, and an x-value of 9 (exactly as above). We do this by wrapping a function around the sequence of operations above. The return value of this function is the computed y value:

    def y():
        m = 5
        x = 9
        b = -3
        return m * x + b

There are a few things to note. The keyword def indicates that a function definition is occurring. The name of this particular function is y. The names of the things being sent to the function are given between the parentheses; since there is nothing between the parentheses, we don't need to send any information to this function when we use it. Together, the first line is known as the function signature, which tells you the name of the function and how many values it expects to be sent when it is used.

The stuff indented from the first line of the function definition is called the function body and is the code that will be evaluated (or executed) when the function is used. You must remember this: the function body is not evaluated until the function is actually used.

Once the function is defined, we can find the value of y repeatedly. Let's assume the function was entered into the file named line.py.

First we import the code in line.py with the from statement:

    >>> from line import *   # not line.py!

This makes the python interpreter behave as if we had typed in the function definition residing in line.py directly into the interpreter. Note, we omit the .py extension in the import statement.

After importing the y function, the next thing we do is use it:

    >>> y()
    42
    >>> y()
    42

The use of a function is known as a function call. Most function calls look similar: the name of the function to be called followed by a parenthesized list of information to send the function so that it can do its job. Thus, the parentheses after the y indicate that we wish to call the y function and get its value. Because we designed the function to take no information when called, we do not place any information between the parentheses.

Note that when we call the y function again, we get the exact same answer.

The y function, as written, is not too useful in that we cannot use it to compute similar things, such as the y-value for a different value of x. This is because we "hard-wired" the values of b, x, and m in the definition of the function. We can improve the y function by passing in the value of x instead of hard-wiring the value to 9.

Passing arguments

A hallmark of a good function is that it lets you compute more than one thing. We can modify our y function to take in the value of x in which we are interested. In this way, we can compute more than one value of y. We do this by passing in some information. This information that is passed to a function in a function call is known as an argument13, in this case, the value of x.

    def y(x):
        slope = 5
        intercept = -3
        return slope * x + intercept

Note that we have moved the variable x from the body of the function to between the parentheses. We have also refrained from giving it a value since its value is to be sent to the function when the function is called. What we have done is to parameterize the function to make it more general and more useful. The variable x is now called a formal parameter since it sits between the parentheses in the first line of the function definition.

Now we can compute y for an infinite number of x's:

    >>> from line.py import *
    >>> y(9)
    42
    
    >>> y(0)
    -3
    
    >>> y(-2)
    -13

What if we wish to compute a y-value for a given x for a different line? One approach would be to pass in the slope and intercept as well as x:

    def y(x,m,b):
        return m * x + b

Now we need to pass even more information to y when we call it:

    >>> from line.py import *
    >>> y(9,5,-3)
    42
     
    >>> y(0,5,-3)
    -3

If we wish to calculate using a different line, we just pass in the new slope and intercept along with our value of x. This certainly works as intended, but is not the best way. One problem is that we keep on having to type in the slope and intercept even if we are computing y-values on the same line. Anytime you find yourself doing the same tedious thing over and over, be assured that someone has thought of a way to avoid that particular tedium. If so, how do we customize our function so that we only have to enter the slope and intercept once per particular line? We will explore one way for doing this. In reading further, it is not important if you understand all that is going on. What is important is that you know you can use functions to run similar code over and over again.

Creating functions on the fly (ADVANCED)

Since creating functions is hard work (lots of typing) and Computer Scientists avoid unnecessary work like the plague, somebody early on got the idea of writing a function that itself creates functions! Brilliant! We can do this for our line problem. We will tell our creative function to create a y function for a particular slope and intercept! While we are at it, let's change the variable names m and b to slope and intercept, respectively:

    def makeLine(slope,intercept):
        def y(x):
            return slope * x + intercept
        return y    # the value of y is returned, y is NOT CALLED!

The makeLine function creates a y function and then returns it. Note that this returned function y takes one value when called, the value of x.

So our creative makeLine function simply defines a y function and then returns it. Now we can create a bunch of different lines:

    >>> from line.py import *
    >>> a = makeLine(5,-3)
    >>> b = makeLine(6,2)

    >>> a(9)
    42
    
    >>> b(9)
    56

    >>> a(9)
    42

Notice how lines a and b remember the slope and intercept supplied when they were created.14 While this is decidedly cool, the problem is many languages (C, C++, and Java included15) do not allow you to define functions that create other functions. Fortunately, Python does allow this.

While this might seem a little mind-boggling, don't worry. The things you should take away from this are:

This last point is very important. Whoever calls a function needs to handle the return value either by assigning it to a variable or by passing it immediately to another function (nested function calls). Here is an example of the former:

    y = square(x)
    z = square(y)

and here is an example of both the former and the latter:

    z = square(square(x))

Both approaches yield identical results.

The Function and Procedure Patterns

When a function calculates (or obtains) a value and returns it, we say that it implements the function pattern. If a function does not have a return value, we say it implements the procedure pattern.

Here is an example of the function pattern:

    def square(x):
        return x * x

This function takes a value, stores it in x, computes the square of x and returns the result of the computation.

Here is an example of the procedure pattern:

    def greeting(name):
        print("hello,",name)
        return "done"

We use a special return value to indicate procedures, the string "done". Returning "done" is meant to indicate the return value should not be used.

Almost always, a function that implements the function pattern does not print anything, while a function that implements the procedure pattern often does16. A common function that implements the procedure pattern is the main function17.

A mistake often made by beginning programmers is to print a calculated value rather than to return it. So, when defining a function, you should ask yourself, should I implement the function pattern or the procedure pattern?

Most of the functions you will implement in this class follow the function pattern.

Another common mistake is to inadvertently implement a procedure pattern when a function pattern is called for. This happens when the return keyword is omitted.

    def psquare(x):
        x * x

While the above code looks like the function pattern, it is actually a procedure pattern. What happens is the value x * x is calculated, but since it is not returned, the newly calculated value is thrown away (remember the throw-away pattern?).

Calling this kind of function yields a surprising result:

    >>> x = psquare(4)
    >>>print(x)
    None

When you do not specify a return value, Python will supply one for you anyway. This supplied value is the special symbol None. Since psquare did not explicitly return a value, None was returned and assigned to the variable x. However, you should always use an explicit return.

Usually, the procedure pattern causes some side-effect to happen (like printing). A procedure like psquare, which has no side-effect, is a useless function.

lusth@cs.ua.edu


Conditionals Top Python Programs and Using Files Contents