Go to top, next, previous, or johnstachurski.net

Functions

Functions are pieces of code which perform specific tasks.

We have already met some functions:

It is also useful to be able to define our own functions

For example, let's say you write a program to send an email to your mother on her birthday

Your mother is happy, because you never forget her birthday

Now you decide you want to send an email on your father's birthday, sister's birthday, etc.

If you write a new program each time you will repeat a lot of the same code

Better to write one function email(name) which sends one email

The argument name can be any string

The program looks something like this (in pseudocode)

birthdays = {'mum': '02-07',
             'dad': '12-03',
             'sis': '07-11'}

define function email(name):
    message = 'Happy birthday %s!!' % name
    address = '%s@gmail.com' % name
    send message to address  # This pseudocode is pretty easy to code up in Python

repeat every morning:
    get date
    for name, birthday in birthdays.items():
        if birthday == date:
            email(name)

Enhancing Clarity with Functions

Let's look at some examples how functions improve clarity

Example 1

Recall that we previously studied the following problem

The solution we used was

## Filename: circle.py
## Author: John Stachurski

# Since pi = area / radius**2, we need to estimate the area of the circle
# and then divide by radius**2 = (1/2)**2 = 1/4.  First we estimate the area
# by sampling bivariate uniforms and looking at the fraction that fall into
# the circle.

from random import uniform
from math import sqrt

n = 100000

count = 0
for i in range(n):
    U, V = uniform(0, 1), uniform(0, 1)
    d = sqrt((U - 0.5)**2 + (V - 0.5)**2)
    if d < 0.5:
        count += 1

area_estimate = count / float(n)

print area_estimate * 4  # dividing by radius**2

At first glance it's not so clear what this program does

Let's try to make it more readable using a function

## Filename: circle2.py
## Author: John Stachurski

from random import uniform
from math import sqrt

# First we define a new function, called in_circle

def in_circle(x, y):
    """
    Tests whether (x,y) is in the circle of radius 0.5, centered on 
    the point (0.5, 0.5).
    """
    if sqrt((x - 0.5)**2 + (y - 0.5)**2) < 0.5:
        return True
    else:
        return False

# Now the main loop

n = 100000
count = 0
for i in range(n):
    U, V = uniform(0, 1), uniform(0, 1)
    if in_circle(U, V):
        count += 1

area_estimate = count / float(n)
print area_estimate * 4  # dividing by r**2

Okay, so the code is longer, but the logic is clearer (which is more important)

We define a function in_circle()

In the main loop we call the function

The syntax for defining functions is explained below

Example 2

Previously we considered the following problem

Next we wrote a program to estimate average payoff

## Filename: 3heads3.py
## Author: John Stachurski

from random import uniform

n = 100000

outcomes = []
for i in range(n):
    payoff = 0
    count = 0
    for j in range(10):
        U = uniform(0, 1)
        count = count + 1 if U < 0.5 else 0
        if count == 3:
            payoff = 1
            break
    outcomes.append(payoff)

print sum(outcomes) / float(n)

The main loop is long and complicated

We can clarify the logic by generating payoffs as a function:

## Filename: 3heads4.py
## Author: John Stachurski

from random import uniform

def sample():
    """
    Generates random payoff from one round of the game.
    If 3 consecutive heads occur, returns 1. Else, returns 0.
    """
    payoff = 0
    count = 0
    for j in range(10):
        U = uniform(0, 1)
        count = count + 1 if U < 0.5 else 0
        if count == 3:
            payoff = 1
            break
    return payoff

# Main loop

n = 100000
outcomes = [sample() for i in range(n)]  # The loop
print sum(outcomes) / float(n)

Again, the program is a bit longer, but the logic is clearer

Notice that the function sample() has no arguments

Defining and Calling Functions

The syntax used to define a function is

def <name>(<parameters>):  # `def` is a Python keyword used to declare functions
    <body>

Here <parameters> is a list of zero or more names/identifiers

Let's look at an example that replicates the count() method for strings

def f(string, letter):
    count = 0
    for s in string:
        if s == letter:
            count += 1
    return count

Calling f(string, letter) is the same as string.count(letter)

Suppose this is written in a script and we run it

Here we run it in IDLE



The function object is created and f is bound to it

>>> f
<function f at 0x86e5f0c>

The function object has been stored at memory location 0x86e5f0c

>>> type(f)
<type 'function'>

Now we can call (i.e., use) f

>>> f('godzilla', 'g')  # Calling f()
1
>>> y = f('foo', 'o')
>>> y
2

Flow

Here's our function definition again

def f(string, letter):
    count = 0
    for s in string:
        if s == letter:
            count += 1
    return count

Let's step through execution of the statement

>>> y = f('foo', 'o') 

Typically, execution of the function body continues until it hits return

def f():
    print 'This line is printed'
    return 1
    print 'But this line is not'

It is possible to have no return statement

def f():
    print 'foo bar'

In this case

We can have multiple return statements

def f(x):
    if x < 0:
        return 'negative'
    else:
        return 'nonnegative' 

Arguments to functions

Any objects can be passed to a function as arguments

We have seen functions passed numbers and strings

Lists and tuples are okay too

Here's a function which is passed a list/tuple X and returns sum(X)

def sum2(X):
    count = 0
    for x in X:
        count += x
    return count

In fact we can pass functions as arguments just as easily!

def function_one(function_two):
    for i in range(5):
        print function_two(i)

def g(n):
    return 'foo' * n

Here is what happens

john@godzilla:~$ python -i test.py 
>>> function_one(g)

foo
foofoo
foofoofoo
foofoofoofoo

Can you see how this works?

Return Values

Functions always return one object

Returning two or more objects is not possible

For example, this doesn't return two values (why?)

def f():
    return 1
    return 2

Here is another attempt to return two objects (integers):

def f():
    return 1, 2

But this returns one object: a tuple

Doc Strings

The syntax for defining functions was given above as

def <name>(<parameters>):
    <body>

Actually there is another optional element: a doc string

def <name>(<parameters>):
    "<doc string>"
    <body>

Previously we saw the folloing example

def in_circle(x, y):
    """
    Tests whether (x,y) is in the circle of radius 
    0.5, centered on (0.5, 0.5).
    """
    if sqrt((x - 0.5)**2 + (y - 0.5)**2) < 0.5:
        return True
    else:
        return False

Single quotes, double quotes, triple quotes all fine

The doc string is just a comment, so we could also use a hash

def in_circle(x, y):
    # Tests whether (x,y) is in the circle of radius 
    # 0.5, centered on (0.5, 0.5).
    if sqrt((x - 0.5)**2 + (y - 0.5)**2) < 0.5:
        return True
    else:
        return False

The advantage of a doc string is that it is available at runtime

>>> in_circle
<function in_circle at 0x866048c>
>>> help(in_circle)
Help on function in_circle in module __main__:

in_circle(x, y)
    Tests whether (x,y) is in the circle of radius 
    0.5, centered on (0.5, 0.5).

Exercises

Problem 1:

Write a function which takes a string as an argument and returns the number of capital letters in the string

Problem 2:

Write a function which takes two sequences A and B as arguments and returns True if every element in A is also an element of B, else False

Problem 3:

Write a function which takes as arguments

Returns

Here is an example with n = 3



Solutions

Solution to Problem 1:

def f(string):
    count = 0
    for letter in string:
        if letter == letter.upper():
            count += 1
    return count

Solution to Problem 2:

def f(A, B):
    subset = True
    for a in A:
        if a not in B:
            subset = False
    return subset

Solution to Problem 3:

## Filename: linapprox.py
## Author: John Stachurski

from __future__ import division

def linapprox(f, a, b, n, x):
    """
    Evaluates piecewise linear interpolant of f at x on the interval [a, b],
    with n evenly spaced grid points.
    Parameters: 
        f is a function
        x, a and b are numbers with a <= x <= b
        n is an integer.
    Returns: 
        A number (the interpolant evaluated at x)
    """
    length_of_interval = b - a
    num_subintervals = n - 1
    step = length_of_interval / num_subintervals  # Distance between grid points

    # Find the first grid point that is larger than x
    point = a
    while point <= x:
        point += step
    # Now x must lie between the gridpoints (point - step) and point
    u, v = point - step, point  

    return f(u) + (x - u) * (f(v) - f(u)) / (v - u)