You are on page 1of 9

Day 3: Functions

In the previous two days, youve learnt the building blocks of programming: the concept of variables, operators and
control structures. Every program ever written consists of solely these components: however, its the way that we
put them together than can define a well-written, robust and powerful program, versus a poorly-written, slapdash
program that is difficult to maintain as a project gets larger.
In todays lecture we introduce functions, which are useful (and some would say necessary) when we start writing
more complicated programs, as well as more complex control structures.

What is a function?
The most basic definition of a function is a piece of code that performs a specific process. Usually, this includes
passing information to the function and then receiving some sort of output or modification to the state of the process.
The typical basic structure of a function, regardless of programming language1 , is:
function header: also known as the function signature, containing the name of the function and the inputs
and/or outputs. The structure of the header varies from language to language, with some languages not
requiring anything more than the function name: Python requires the function name and (optionally) inputs.
body: The statements to be executed by the function. Note that, as Python is a white-space sensitive language,
the contents of the block are delimited by tabbed space, whereas other languages will delimit the block by the
use of brackets.
return statements (optional): Not always necessary within a function, but when executed the control flow
will exit the function immediately after executing this line.
An example for Python is shown below, based on the first lines of Python code we wrote on the first day.
# function header
def hello_name(name):
# Docstring to tell us what is in the function (optional)
"""This function returns ap personal greeting given a name."""
# body of function
greeting = 'Hello ' + name
print greeting
print "It's nice to meet you"
# return
statement
return greeting

1 lf

youre interested to see how functions vary across programming languages, check out http://www.roesler-ac.de/wolfram/hello.htm

Bernstein Center Freiburg

Scientific Programming in Python

2011

The Advantages of Using Functions


Why do we want to use functions:
To avoid repetition: using well defined functions can save you time within your project and even accross
several projects and reduce the chance of making mistakes.
To make the code more readable: when you structure your code in well defined functions you create a
structure which makes your code more pleasant for yourself and for others.
To be able to share the code: When your code is packaged in functions it is easier to share it with others.
Technically, we could not use functions and write out the same code as many times as necessary, all in one big file.
However, while this is feasible for small programs, this approach becomes problematic (if not horrible!) by the time
we start getting to programs that span one or two modules, and downright impossible once we start writing larger
programs.
The immediate advantages that functions offer are firstly that we can reuse code: we dont have to rewrite the same
code several times, which saves time and more importantly reduces the possibility of making errors as we have higher
code coverage. From a writing perspective, they make the code we write more legible: we can more clearly see the
control flow of the program, with functions being executable units within it.
If we look at the bigger picture, the advantages of functions are that they become very important when we start
needing to use scope, for example, in object oriented code. This will be introduced on later on in the course.
To illustrate the immediate advantages of using a function, lets extend on the example function above. While the
initial example is trivial if we just want to greet ourselves, consider if we wanted to say hello to a lot of people.
Instead of typing the same code out again i.e.
print
print
print
print
print
print

"Hello Abel"
"It's nice to meet you"
"Hello Baker"
"It's nice to meet you"
"Hello Charlie"
"It's nice to meet you"

we can instead use:


for person in 'Abel', 'Baker', 'Charlie':
hello_name(person);

How much should I include in a function?


One of the difficult things to work out, especially when first starting to use functions, is how much should I include
in a function?". A good rule of thumb is that a function should only have one purpose: for example, to calculate
something, or print some information. Furthermore, it should be code that you may need to use again, even if not in
the immediate future. If you start including many things in a function, you may want to consider breaking it down
into several smaller functions.
A good hint that youve got the right amount inside a function is that youre able to name the process easily.
Typically, naming functions in Python, like any other language, consists of a brief description i.e. calc_distance,
get_student_id, set_color, simulate_epsp, is_stimulated. If you cant think of a name or describe
the function in two words, then consider whether your function is too big or not.
2

Bernstein Center Freiburg

Scientific Programming in Python

2011

Important! : As Python is a runtime language rather than a compiled one, we have to make sure that we apply the
same rules as we learnt with variables: we cant call a function if it hasnt already been seen by our Python program.
This still holds true when writing functions in files.

Input and Output


Everything can be passed to and from a function in Python: every data type that weve come across (strings, ints,
floats, bools) through to tuples and objects (see tomorrows and Fridays lecture) - and (very helpfully!) functions
too.
Passing information to a function
Input is passed to functions by first specifying the arguments (or expected inputs) in the function header, and then
passing parameters (or variables, values, objects, etc.) in when we call the function. Note that we can define a
function as having multiple arguments, but once we specify the order in the function header, we have to make sure
that the parameters we pass match this order.
So, if we go back to our previous example and extend it, we can have:
def hello_name_age(name, age):
"""Prints a greeting and a complement"""
print "Hello", name
print "Are you really only ", age, "?"
>>> name = ' Bob '
>>> age = '42'
>>> # Ex1: if we keep the same order, we get
>>> hello_name_age(name, age)
Hello Bob
Are you really only 42?
>>> # Ex2: but if we get the order wrong ...
>>> hello_name_age(age, name)
Hello 42
Are you really only Bob?

Question: Why did the function above still get the order the wrong way round, even though we passed it variable
called name and age, the same argument names that were already defined in the function signature?
Completely accurate answer: because the scope is different.
More understandable answer: because the name of the argument isnt the same as the variable that we pass to
the function. From outside the function, we have no information about the contents of the function (in this instance,
at least: note that this isnt always the case, as will be explained further below). Alternatively, consider that the
function is only concerned with the ordering of the parameters, not their name. The concept of scope is important
when we start writing more complex and ambitious bits of code, and will be introduced a little bit later today.
Handling extra parameters
One really nice feature of Python is that we can define a function as having some defined arguments, but still allow
for the possibility of extra additional but unnamed arguments too. This is done by using the star modifier, like
2
*extraParams, which then converts everything that is left-over into a left-over collection . Note that if you do
use this modifier, the star-modified argument has to be the last argument.
2 Technically,

it is converted into a tuple, which you will learn about tomorrow

Bernstein Center Freiburg

Scientific Programming in Python

2011

def hello_name_info(name, *otherlnfo):


print
"Hello", name
print
"Your information is:"
print otherlnfo
>>> hello_name_info( ' Arthur', '42', 'Babelfish', 'blue')
Hello Arthur
Your information is: ('42', 'Babelfish', 'blue')
>>> hello_name_info ('Ford', 'writer', '42', 'towel')
Hello Ford
Your information is : (' writer ', '42 ', ' towel ')

While this doesnt seem very useful in the above example, it does become useful when we dont quite know what we
will be passing each time and/or including many parameters.
Using default values
Sometimes it may be the case that we have a lot of cases when the function will take in the same parameter values,
and only a few times when there will be exceptions: for instance, we define a hello_student but we know that
most times that the function will be used will be for students that are 22-yrs old and study biology. One way that
we can allow for this is to define default values in the function signature, as shown below:
def hello_student(name, studies="biology", age=22):
print "Hello", student
print "You are", age, "and study", studies

We can then call hello_student with either of the following:


>>> hello_student('Bob')
>>> hello_student('Ford', 'journalism')
>>> hello_student('Arthur', 'teatowels', '32')

Note that as there is no default value defined for name, name is a required argument; therefore, we must define
name everytime that we call hello_student.
Also, arguments with default values should always go after required arguments - consider the case where the alternative
would be allowed.
Getting around the problem of order
Sometimes though, it becomes annoying to have to worry about the order that we pass things to a function. Python
nicely allows us to do this, using keyword arguments.
Extending on the hello_student example that we have above, we can make function calls as the following:
>>> hello_student("Slartibartfast", age = "3000", studies = "geography")
>>> hello_student(age = 27, name = "Trillian")

However, be aware that even though were using keywords, required arguments are still required. Also, there are a
couple of other constraints i.e. required arguments should also be at the beginning, etc.
The following examples would not be valid:
4

Bernstein Center Freiburg

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

Scientific Programming in Python

2011

hello_student()
#Error: no required argument
hello_student(studies = "galactic president", "Zaphod")
#Error: required argument follows keyword
hello_student("Beeblebrox", name = "Zaphod")
#Error: duplicate argument
hello_student(name = "Zaphod", number_of_heads = 2)
#Error: unknown keyword

Passing functions to a function


The other super-super-useful thing that Python can do is to allow us to pass functions names as parameters, which
can then be called.
def calc_rect_area(height, width):
return height*width
def calc_triangle_area(height, width):
return 0.5*height*width
def calc_geo_area(shape):
if(shape == 'rectangle'):
return calc_rect_area
elif(shape == 'triangle'):
return calc_triangle_area
def calc_area (shape, height, width):
fn = calc_geo_area(shape)
area = fn(height, width)
print area

Then we can call it like


>>> calc_area('rectangle', 4, 5)
20
>>> calc_area('triangle', 4, 5)
10

Returning values
Unlike some other programming languages, Python doesnt make us say in the function signature whether we are
going to return a value or not. We dont always have to return a value - for instance, maybe the function just prints
information about something and then returns to the function that called it - but if we do have to return some values,
we do this using the reserved word return, followed by the values to be returned.
Something that we can do in Python (that we cant do in a lot of other languages) is to return multiple values3 . We
return mulitple values in the following manner:
3 However,

this really isnt good practice ...

Bernstein Center Freiburg

Scientific Programming in Python

2011

a, b, c = 0, 0, 0
def getabc():
a = "Hello"
b = "World"
c = "!"
return a,b,c
a,b,c = getabc()

A better way of doing return values is to first define a tuple and then return that, but well learn about tuples
tomorrow. In the meantime, just be careful and consistent if you start using multiple return values.
One last thing: make sure that the return keyword is always in the last line that you expect to execute, as
once function encounters a return keyword, it exits and the lines after return are not executed.

Scope and namespace


So, previously we touched on the concept of scope, and mentioned that from outside the function, we couldnt really
see the contents of the function as the variables inside were outside of our view. The view is more formally known
as the scope of a variable, and is important to understand. Closely related to scope is the concept of namespace,
which can be defined as the mapping between names to objects - such as variables or functions.
The scope of an object defines in how many places we can directly access the namespace4 . Generally, we work from
most local to most global or default, or from:
1. the current function (also known as the local scope)
2. to the current module (also known as the global scope)
3. to parent modules,
4. then finally to built-in namespace.
In Python, we can check what global and local variables there are using the commands globals() and locals():
both will list the namespaces at the corresponding scope. To hopefully illustrate the differences, consider the example
below:
some_var = 27
def demo(in_val):
demo.val = 16 # An attribute accessible from main code
some_var +=1
another_var = 12 #A local variable, independent of main code
result = in_val+14
return result
anotherVar =
pVal = 16
print
print
print
print

17 # not accessed in function


# accessed in function via parameter

demo(pVal)
# 30
val # will give an error
demo.val # will print 16
some_var # will print 28

4 NB that we say directly access the namespace, as its generally possible to access variables and functions of modules that we wouldnt
otherwise be accessible by default, by fully qualifying their location i.e. module.submodule.function() or module.attribute

Bernstein Center Freiburg

Scientific Programming in Python

2011

print another_var # will print 17


print demo.anothervar #will give an error

Warning: be extra aware of namespaces in Python One of the great things about Python is that we dont need
to worry about explicitly declaring variables, as we do in other languages i.e. Java, C++. However, one of the really
bad things about Python is: we dont explicitly declare variables, so they can creep into places that we wouldnt
necessarily expect them. Take, for instance, the snippet of code below:
one = 1
if one == 1:
two =
print
print
else:
print

2
one #
two #

will print 1
will print 2

one

print
one # will print 1
print two # will print 2

While in many languages, the variable two wouldnt exist until we enter the body of the for loop and is then
immediately destroyed at the end of the block, thats not quite the case in Python - hence we can still print variable
two. All fine and good so far. But what happens if we change the value of variable one to something other than
1? The contents of variable one will be printed, and then an error will be thrown as the variable two hasnt been
initiated.
The easiest and nicest way to avoid this happening is to be consistent in your naming and declaring of variable: keep
any statements which reference variables or attributes within the same level of a control structure.

Bernstein Center Freiburg

Scientific Programming in Python

2011

Recursive Functions
Recursive functions are a neat and powerful way of programming, and exploit the idea of recursion where applying a
function is a part of the definition of that same function.
The simplest recursive mathematical function is the factorial function, where n! is defined as being the product of all
the integers from 1 to n inclusive. Another way of looking at this is that n! is really just n times (n-1)!, and (n-1)!
is a easier to calculate than n!, right?
So, another way of determining n! is to determine (n-1)!, for n 1. If n = 1, then we should return 1. In function
form, we would write
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)

Note that its vital to include a stopping case within your recursive function. For instance, if we left out the if
case which tested for n==1, n would continue to decrease and we would get stuck in an infinite loop (this will also
happen currently if we enter a negative number).
Well be dealing with recursive functions in a more hands-on way in the exercises.

Extension: Further function-related concepts


Weve included here things that are related to functions and we think that would be helpful to you to know, but
that may be easier for you to come back to after completing Days 4 and 5. However, as some of you may be feeling
adventerous enough, weve included them here:
Passing named arguments to a dictionary
Occasionally you may have to pass around a lot of parameters to a program which get passed around between
functions. Rather than explicitly listing keyword arguments and using the star modifier, we can use another form of
input and instead pass named arguments which get pushed into a dictionary (which you will learn about tomorrow).
From there, we will be able to use the dictionary as a store for variables that we may need to access.
This technique is done by using the double-asterisk (**kw) parameter within the function signature:
def sample_function(a, b, c, *args, **kwargs):
print a, b, c, args, kwargs
sample_function(1,2,3,4,5,6,7,8,9,timestep=10,meaning_life=42)
#prints '1, 2, 3, (4, 5, 6, 7, 8, 9), {"timestep": 10, "meaning_life": 42}'

Such function signatures are useful when we want to pass a lot of parameters along but dont want to use them right
away.
Lambda functions (introduced properly in tomorrows lecture)
Sometimes we may have to perform a small operation multiple times. The operation is small enough that we may not
want to bother writing a proper function for it, but its unique enough that we dont want to have to bother writing
the same expression out each time. Python provides an alternative: lambda functions, which are like mini-functions
that return the result of a single expression.
8

Bernstein Center Freiburg

Scientific Programming in Python

2011

The signature for lambda functions, using the simple example of addition, is:
# function signature:
function_name = lambda variable : expression
# Example
lambda_add = lambda a, b: a+b
# Using a lambda function
print lambda_add(1,2)
# prints 3

Generator functions
Consider the case where we want to run a function over a loop that creates lots of data that we end up having to
store. However, it may be the case that we only need a small subset of that data at any one time and dont need to
keep track of all the values. For this purpose, Python provides generator functions, which dont run to completion
when they are first called, but instead runs only until it has a variable to return. It then yields the value back and
pauses or suspends until it is called again - rather like a function-on-demand.
def my_gen(value=10)
""" Counts by incrementing my_value by value"""
print "Initiated generator my_gen"
my_val = 0
while True:
yield my_val
my_val += value
count_10 = my_gen(10)
print count_10.next()
# prints 0
print count_10.next()
# prints 10

A nice example is also included in http://docs.python.org/reference/expressions.html, which further explains how to


modify and send values to generator functions.

What you should know from todays lecture:


Identify the different components of a function signature;
Pass arguments to a function, and correctly use default values, keyword arguments and star modifiers;
Return values from a function using the return keyword;
Understand the concept of scope and namespace;
Identify and implement recursive functions (including the stopping case!);
(Additional: Pass arguments within a function to a dictionary using **kwargs and generator functions)