6. Function

Functions

In Python, a function is a named sequence of statements that belong together.

The syntax for a function definition is:

def name( parameters ):
    statements
def name( parameters ):
    statements

A header line: begins with a keyword(def( and ends with a colon.

A body: consisting of one or more Python statements, each indented the same amount – 4 spaces is the Python standard – from the header line.

Function definition explained

name:

anything you like, but must follow the rules for legal identifiers

parameters:

specify what information the function needs to do its work.

statements:

There can be any number of statements inside the function, but they have to be indented from the def.

Parameters: Formal vs Actual

Formal parameters

In the definition, the parameter list is more specifically known as the formal parameters. This list of names describes those things that the function will need to receive from the user of the function.

Actual parameters

When you use a function, you provide values to the formal parameters. These values, often called arguments or actual parameters, are passed to the function by the user.

Example

import turtle

def drawSquare(t, sz):
    """Make turtle t draw a square with side sz."""

    for i in range(4):
        t.forward(sz)
        t.left(90)

alex = turtle.Turtle() 
drawSquare(alex, 50)
In [1]:
from IPython.display import IFrame
IFrame('http://162.105.175.204:8000/runestone/static/thinkcspy/Workspace/index.html', width='100%', height=450)
Out[1]:

docstrings

By convention, Python programmers use docstrings for the key documentation of their functions.

If the first thing after the function header is a string (mostly a triple-quoted string), it is called a docstring and gets special treatment in Python.

Try <function_name>.__doc__ in Python shell, will retrieve the docstring for the function.

This is different from comments in your code, which are completely eliminated when the program is parsed.

Function call

Defining a new function does not make the function run.

To do that we need a function call.

This is also known as a function invocation.

import turtle
def drawSquare(t, sz):
    """Make turtle t draw a square with side sz."""
    for i in range(4):
        t.forward(sz)
        t.left(90)

alex = turtle.Turtle() 
drawSquare(alex, 50)          
alex.penup()
alex.goto(100,100)
alex.pendown()
drawSquare(alex,75) # Draw another square

Exercise

1. What is a function in Python?
(A) A named sequence of statements.
(B) Any sequence of statements.
(C) A mathematical expression that calculates a value.
(D) A statement of the form x = 5 + 4.
2. What is one main purpose of a function?
(A) To improve the speed of execution
(B) To help the programmer organize programs into chunks that match how they think about the solution to the problem.
(C) All Python programs must be written using functions
(D) To calculate values.
3. Which of the following is a valid function header (first line of a function definition)?
(A) def drawCircle(t):
(B) def drawCircle:
(C) drawCircle(t, sz):
(D) def drawCircle(t, sz)
4. True or false: A function can be called several times by placing a function call in the body of a loop.
(A) True
(B) False

Functions that Return Values

In [ ]:
print(abs(-5))
print(sum([1,2,3,4,5]))
print(sum(range(101)))
print(sorted([1,2,4,9,6]))
In [ ]:
import math

print(math.pow(2, 3))
print(math.pow(7, 4))
In [ ]:
print(max(7, 11))
print(max(4, 1, 17, 2, 12))
print(max(3 * 11, 5 ** 3, 512 - 9, 1024 ** 0))

Fruitful function

Functions that return values are sometimes called fruitful functions.

In many other languages, a chunk that doesn’t return a value is called a procedure. In Python we still call it a function, or if we want to stress it, a non-fruitful function.

Fruitful functions still allow the user to provide information (arguments).

However there is now an additional piece of data that is returned from the function.

$\require{AMScd}$ \begin{CD} @>\text{needs}>> \text{Func} @>\text{return}>> \end{CD}

Local variable

In [ ]:
def square(x):
    y = x * x
    return y

toSquare = 10
result = square(toSquare)
print("The result of ", toSquare, 
      " squared is ", result)

Step through

In [2]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20y%20%3D%20x%20*%20x%0A%20%20%20%20return%20y%0A%0AtoSquare%20%3D%2010%0Aresult%20%3D%20square(toSquare%29%0Aprint(%22The%20result%20of%20%22,%20toSquare,%20%22%20squared%20is%20%22,%20result%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=300)
Out[2]:

More about return value

Typically, functions will return values that can be printed or processed in some other way by the caller.

All Python functions return the value None unless there is an explicit return statement with a value other than None.

In [ ]:
def square(x):
    y = x * x
    print(y)  # Bad!

toSquare = 10
result = square(toSquare)
print("The result of ", toSquare, 
      " squared is ", result)
In [3]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20y%20%3D%20x%20*%20x%0A%20%20%20%20print(y%29%20%20%20%23%20Bad!%20%0A%0AtoSquare%20%3D%2010%0Aresult%20%3D%20square(toSquare%29%0Aprint(%22The%20result%20of%20%22,%20toSquare,%20%22%20squared%20is%20%22,%20result%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=300)
Out[3]:

Exercise

1. What will the following function return?
def addEm(x, y, z):
    print(x + y + z)
(A) Nothing (no value)
(B) The value of x + y + z
(C) The string 'x + y + z'
2. What is wrong with this func definition:
def addEm(x, y, z):
    return x + y + z
    print('the answer is', x + y + z)
(A) You should never use a print statement in a function definition.
(B) You should not have any statements in a function after the return statement.
(C) You must calculate the value of x+y+z before you return it.
(D) A function cannot return a number.

Variables and Parameters are Local

def square(x):
    y = x * x
    return y

z = square(10)
print(y)
In [4]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20y%20%3D%20x%20*%20x%0A%20%20%20%20return%20y%0A%0Az%20%3D%20square(10%29%0Aprint(y%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=300)
Out[4]:

Variable lifetime

Local variable y only exist while the function is being executed — we call this its lifetime.

When the execution of the function terminates (returns), the local variables are destroyed.

Each call of the function creates new local variables, and their lifetimes expire when the function returns to the caller.

Global variable

y = 10
print(y)
It is legal for a function to access a global variable. But this is considered **bad form** and should be avoided.
In [ ]:
def badsquare(x):
    y = x ** power
    return y

power = 2
result = badsquare(10)
print(result)

Rewrite the above code, avoid using global variables.

how variables are looked up in Python

  • First, Python looks at the variables that are defined as local variables in the function. We call this the local scope.

  • If the variable name is not found in the local scope, then Python looks at the global variables, or global scope.

Assignment statements in the local function cannot change variables defined outside the function.

In [5]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20powerof(x,%20p%29%3A%20%0A%20%20%20%20power%20%3D%20p%20%20%20%23%20Another%20dumb%20mistake%0A%20%20%20%20y%20%3D%20x%20**%20power%0A%20%20%20%20return%20y%0A%0Apower%20%3D%203%20%0Aresult%20%3D%20powerof(10,%202%29%0Aprint(result%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=450)
Out[5]:

Another example

def square(x):
    y = x * x
    x = 0       # assign a new value to x
    return y

x = 2 
z = square(x)
print(z)
In [6]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20y%20%3D%20x%20*%20x%0A%20%20%20%20x%20%3D%200%20%20%20%20%20%20%20%23%20assign%20a%20new%20value%20to%20the%20parameter%20x%0A%20%20%20%20return%20y%0A%0Ax%20%3D%202%20%0Az%20%3D%20square(x%29%0Aprint(z%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=450)
Out[6]:

Access global variable

using global keyword to declare a variable as global

In [ ]:
g = 10
def testg():
    global g
    g = 100
testg()
print(g)

Exercise

1. What is a variable’s scope?
(A) Its value
(B) The range of statements in the code where a variable can be accessed.
(C) Its name
2. What is a local variable?
(A) A temporary variable that is only used inside a function
(B) The same as a parameter
(C) Another name for any variable
3. Can you use the same name for a local variable as a global variable?
(A) Yes, and there is no reason not to.
(B) Yes, but it is considered bad form.
(C) No, it will cause an error.

The Accumulator Pattern

In [ ]:
def square(x):
    runningtotal = 0
    for counter in range(x):
        runningtotal = runningtotal + x

    return runningtotal

toSquare = 10
squareResult = square(toSquare)
print("The result of", toSquare, 
      "squared is", squareResult)

Accumulator

This pattern of iterating the updating of a variable is commonly referred to as the accumulator pattern. We refer to the variable as the accumulator.

Remember that the key to making it work successfully is to be sure to initialize the variable before you start the iteration.

Once inside the iteration, it is required that you update the accumulator.

In [7]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20runningtotal%20%3D%200%0A%20%20%20%20for%20counter%20in%20range(x%29%3A%0A%20%20%20%20%20%20%20%20runningtotal%20%3D%20runningtotal%20%2B%20x%0A%0A%20%20%20%20return%20runningtotal%0A%0AtoSquare%20%3D%2010%0AsquareResult%20%3D%20square(toSquare%29%0Aprint(%22The%20result%20of%22,%20toSquare,%20%22squared%20is%22,%20squareResult%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=400)
Out[7]:

Functions can Call Other Functions

It is important to understand that each of the functions we write can be used and called from other functions we write.

This is one of the most important ways that computer scientists take a large problem and break it down into a group of smaller problems.

This process of breaking a problem into smaller subproblems is called functional decomposition.

In [ ]:
def square(x):
    y = x * x
    return y
    
def sum_of_squares(x, y, z): 
    a = square(x)
    b = square(y)
    c = square(z)
    return a + b + c

a,b,c = -5,2,10
result = sum_of_squares(a, b, c)
print(result)
In [8]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20square(x%29%3A%0A%20%20%20%20y%20%3D%20x%20*%20x%0A%20%20%20%20return%20y%0A%20%20%20%20%0Adef%20sum_of_squares(x,%20y,%20z%29%3A%20%0A%20%20%20%20a%20%3D%20square(x%29%0A%20%20%20%20b%20%3D%20square(y%29%0A%20%20%20%20c%20%3D%20square(z%29%0A%20%20%20%20%0A%20%20%20%20return%20a%20%2B%20b%20%2B%20c%0A%0Aa%20%3D%20-5%0Ab%20%3D%202%20%0Ac%20%3D%2010%0Aresult%20%3D%20sum_of_squares(a,%20b,%20c%29%0Aprint(result%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=500)
Out[8]:

The above example illustrates many very important Python concepts, including:

  • local variables

  • global variables

  • parameter passing

import turtle
def drawRectangle(t, w, h):
    """Get turtle t to draw a rectangle of width w and height h."""
    for i in range(2):
        t.forward(w)
        t.left(90)
        t.forward(h)
        t.left(90)

def drawSquare(tx, sz): 
    drawRectangle(tx, sz, sz)

tess = turtle.Turtle()           # create tess
drawSquare(tess, 50)

This example illustrates an important computer science problem solving technique called generalization.

Why bother to create new functions

  • Creating a new function gives you an opportunity to name a group of statements.

    • Functions can simplify a program by hiding a complex computation behind a single command. The function (including its name) can capture your abstraction of the problem.
  • Creating a new function can make a program smaller by eliminating repetitive code.

    • Sometimes you can write functions to solve a specific problem using a more general solution.

Exercise: Drawing a circle

import turtle

def drawPolygon(t, sideLength, numSides):
    # fill your code here

def drawCircle(anyTurtle, radius):
    # fill your code here

wheel = turtle.Turtle()
drawCircle(wheel, 40)
In [9]:
from IPython.display import IFrame
IFrame('http://162.105.175.204:8000/runestone/static/thinkcspy/Workspace/index.html', width='100%', height=450)
Out[9]:

Flow of Execution Summary

  • Execution always begins at the first statement of the program.

  • Statements are executed one at a time, in order, from top to bottom.

  • Function definitions do not alter the flow of execution of the program, but remember that statements inside the function are not executed until the function is called.

  • Instead of going to the next statement, the flow jumps to the first line of the called function, executes all the statements there, and then comes back to pick up where it left off.

Exercise:

Analyze the execution flow of the following code.

def pow(b, p):
    y = b ** p
    return y

def square(x):
    a = pow(x, 2)
    return a

n = 5
result = square(n)
print(result)
In [10]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=def%20pow(b,%20p%29%3A%0A%20%20%20%20y%20%3D%20b%20**%20p%0A%20%20%20%20return%20y%0A%0Adef%20square(x%29%3A%0A%20%20%20%20a%20%3D%20pow(x,%202%29%0A%20%20%20%20return%20a%0A%0An%20%3D%205%0Aresult%20%3D%20square(n%29%0Aprint(result%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false", width='100%', height=400)
Out[10]:

Using a Main Function

  • Before the Python interpreter executes your program, it defines a few special variables.

  • One of those variables is called __name__ and it is automatically set to the string value "__main__" when the program is being executed by itself in a standalone fashion.

  • If the program is being imported by another program, the __name__ variable is set to the name of that module.

  • Usually we would not want to execute the main function if imported by another program.

In [ ]:
def squareit(n):
    return n * n

def cubeit(n):
    return n*n*n

def main():
    anum = int(input("Please enter a number"))
    print(squareit(anum))
    print(cubeit(anum))

if __name__ == "__main__":
    main()

Exercise

Write a fruitful function sumTo(n) that returns the sum of all integer numbers up to and including n. So sumTo(10) would be 1+2+3...+10 which would return the value 55.

In [ ]:
def sumTo(n):
    # fill the code here
    return result

# Now lets see how well this works
t = sumTo(0)
print("The sum from 1 to 0 is",t)
t = sumTo(10)
print("The sum from 1 to 10 is",t)
t = sumTo(5)
print("The sum from 1 to 5 is",t)

Program Development

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.

Example: distance between two points

The coordinates of $(x_1, y_1)$ and $(x_2, y_2)$

$\text{distance} = \sqrt{(x_2-x_1)^2+(y_2-y_1)^2}$

Example: step 1

def distance(x1, y1, x2, y2):
    return 0.0

Run and test it.

In [ ]:
def distance(x1, y1, x2, y2):
    return 0.0

print(distance(1, 2, 4, 6))

Example: step 2

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    return 0.0

Example: step 3

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    return 0.0

Example: step 4

In [ ]:
def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    result = dsquared**0.5
    return result

print(distance(1, 2, 4, 6))

About the incremental development process

  • Start with a working skeleton 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 that you can easily inspect and check them.

  • Once the program is working, you might want to consolidate multiple statements into compound expressions, but only do this if it does not make the program more difficult to read.

Composition

Functions can be called from within another. This ability to build functions by using other functions is called composition.

Example

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.

radius = distance(xc, yc, xp, yp)
In [ ]:
def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    return dsquared**0.5

def area(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    return 3.14159 * radius**2

print(area(0,0,1,1))

Debugging with print

  • You must have a clear solution to the problem, and must know what should happen before you can debug a program. Work on solving the problem on a piece of paper (perhaps using a flowchart to record the steps you take) before you concern yourself with writing code.

  • Do not write chatterbox functions. A chatterbox is a fruitful function that, in addition to its primary task, also asks the user for input, or prints output, when it would be more useful if it simply shut up and did its work quietly.

Unit testing

It is a common best practice in software development to include automatic unit testing of source code.

Unit testing provides a way to automatically verify that individual pieces of code, such as functions, are working properly.

Unit testing also forces the programmer to think about the different cases that the function needs to handle.

test suite

Extra code in your program which is there because it makes debugging or testing easier is called scaffolding.

A collection of tests for some code is called its test suite.

Unit testing example using assert

def absolute_value(n):
    """ Compute the absolute value of n """
    return abs(n)
In [ ]:
def absolute_value(n):   # Buggy version
    """ Compute the absolute value of n """
    if n < 0:
        return 1
    elif n > 0:
        return n
In [ ]:
def test_suite():
    """ Run the suite of tests for code in this module (this file).
    """
    assert(absolute_value(17) == 17)
    assert(absolute_value(-17) == 17)
    assert(absolute_value(0) == 0)
    assert(absolute_value(3.14) == 3.14)
    assert(absolute_value(-3.14) == 3.14)

test_suite()        # Here is the call to run the tests

Programming with style

  • use 4 spaces (instead of tabs) for indentation
  • limit line length to 78 characters
  • when naming identifiers, use CamelCase for classes and lowercase_with_underscores for functons and variables
  • place imports at the top of the file
  • keep function definitions together
  • use docstrings to document functions
  • use two blank lines to separate function definitions
  • keep top level statements, including function calls, together at the bottom of the program

Glossary

chatterbox function

A function which interacts with the user (using input or print) when it should not. Silent functions that just convert their input arguments into their output results are usually the most useful ones.

composition (of functions)

Calling one function from within the body of another, or using the return value of one function as an argument to the call of another.

dead code

Part of a program that can never be executed, often because it appears after a return statement.

fruitful function

A function that yields a return value instead of None.

incremental development

A program development plan intended to simplify debugging by adding and testing only a small amount of code at a time.

None

A special Python value. One use in Python is that it is returned by functions that do not execute a return statement with a return argument.

return value

The value provided as the result of a function call.

scaffolding

Code that is used during program development to assist with development and debugging. The unit test code that we added in this chapter are examples of scaffolding.

temporary variable

A variable used to store an intermediate value in a complex calculation.

Appendix

Visualize python code execution

http://pythontutor.com/

In [ ]: