Functions in Python

In this notebook, we will explore the implementation of functions in Python.

Learning objectives for this notebook:

  • Student is able to define functions with input parameters to execute a piece of code (use functions as "methods")
  • Student is able to create and use functions that return a value (or multiple values)
  • Student is able to import functions from libraries / modules
  • Student is able to use Shift-Tab to bring up the help for a function from a library
  • Student is able to predict if a variable name in a function refers to a local variable or a global variable

Functions to save typing

In programming, you often want to repeat the same sequence of commands over and over again.

One way to do this is to copy and paste the same piece of code over and over again. This is actually quite easy, but runs quickly into a problem: let's say you want to change a little bit what that code will do, then you need to change it in many places. If you change it in one place but forget in another, then your program might crash, or even worse, may give you the incorrect answer!

For this reason (among others), programming languages allow programmers to define "functions". Functions are pieces of code that you can give a name and then enable you to them use over and over again, without having to retype the code text.

As an example, let's say that we want to print out the value of a variables named a and b using a long sentence:

In [ ]:
a = 6
b = 4
print("The value of variable a is", a)
print("The value of variable b is", b)

a = a/2
b = 3
print("The value of variable a is", a)
print("The value of variable b is", b)

a = a+1
b = 1.5
print("The value of variable a is", a)
print("The value of variable b is", b)

a = a-20
b = -1e4
print("The value of variable a is", a)
print("The value of variable b is", b)

a = a+1j
b = 1
print("The value of variable a is", a)
print("The value of variable b is", b)

(Note that we have used a new feature of the print() command here: if we give it a list of values separated by commas, it will print each of them after each other on a line.)

To save a lot of typing, one can define a simple function to do this work for us. To define a function, you use the following syntax:

def function_name():
     ...

Here, you replace the ... with the code you want to function to execute. In Python the code inside the function should be "indented" by starting each line with a "tab". By default, adding a tab will produce 4 spaces in your code. You can also "indent" your code by manually adding spaces, but you must make sure to add 4 spaces each time. The Jupyter notebook will try to detect if you make a mistake in your indentation, and will sometimes color your text in red if it detects a mistake.

Tabs in Python are VERY IMPORTANT: Python uses tabs to know which code is inside the function and which is not. If you make a mistake with the tabs in such a way that python cannot understand what you mean, it will give you an IndentationError.

Once you have defined your function, you can execute it by using the code function_name().

Let's look at how to use a function as a "procedure" to simplify the code above:

In [ ]:
def print_status():
    print("The value of variable a is", a)
    print("The value of variable b is", b)

a = 6
b = 4
print_status()

a = a/2
b = 3
print_status()

a = a+1
b = 1.5
print_status()

a = a-20
b = -1e4
print_status()

a = a+1j
b = 1
print_status()

In this example, it may not be such a big deal, but you can imagine that as the code in your function becomes more and more complicated, it will save you a lot of time. Also, imagine that I wanted to change the wording of the sentence I print: in the case with the function, I would only have to do this once, while in the example without function, I would have to manually change this at 5 different places.

Exercise 1 Write your own function fieconvert that makes an integer value from a parameter var, multiplies it with two and stores the integer value of var in a new variable intvar. Add print statements in the function and play around with the indentation (add extra tabs and spaces for example) to see how 'critical' Python is with indentation.

Here you will find the first graded answer. Add your code to the answer cell and calculate the solution. In Vocareum you can answer check your total score for this notebook using check answers. This grading is to follow your progress through the course and for you to get feedback on your performance. These exercises do not count for your final course grade.

In [ ]:
var=3.5

###
### Your code here
###

Functions with input variables

Let's say that we wanted to print out the status of variables other than a or b: say we wanted to make a function that could print out the value of ANY variable. How could we do this?

In the example above, our function explicitly printed out variables a and b. But this only works because I know in advance that the person using my function has defined variables a and b. But what if I want to print the value of variable c?

To allow functions to be more generic, and therefore more "reusable" in general, Python allows you to define "input variables" for your function. The syntax for this is the following:

def function_name(x):
    ...

When you do this, for the code INSIDE your function, a variable x will be defined that will have the value given by the input value given to the function by the user. Let's look at a specific example:

In [ ]:
def print_status2(x):
    print("The value passed to the function is", x)

a = 1.5
print_status2(a)

a = 1+1j
print_status2(a)

print_status2(1.5323)

How does this work?

When the function print_status(a) is called, Python "sends" ("passes" in computer speak) the value of a to the function. Inside the function, Python creates a new (temporary) variable called x, that is defined ONLY while the function code is running. This temporary variable x is then assigned the value that was sent to the function, and then the code is executed. When the function is finished, the variable x is destroyed. (Try adding the code print(x) above outside the function and see what happens!)

Note, as you can see in the third example, the things you pass to functions do not even need to be variables! This is fine because the function only needs the value of the argument that is passed to the function.

Exercise 2 Copy your code from exercise 1 into the cell below and change it such that it uses a function with input parameters to achieve the same task.

In [ ]:
# Your code here

Functions with multiple inputs

Functions can also take multiple input variables. To do this, you put them all in between the brackets (), separated by commas. For example, with 3 variables, the syntax is:

def function_name(variable1, variable2, variable3):
    ...

You would then use this function in the following way:

function_name(argument1, argument2, argument3)

When you do this, inside the function, variable1 will get assigned the value of argument1, variable2 will get assigned the value of argument2, and variable3 will get assigned the value of argument3. This matching of the position in the list is called matching by "positional order".

Note that there are several different names used for the "input variables" of a function: often, computer scientists will also use the name "input arguments" (or just "arguments), or "input parameters" (or just "parameters").

In [ ]:
def print_status3(x, y):
    print("The value of the first input variable is ", x)
    print("The value of the second input variable is ", y)

print_status3(1,2)
print_status3(2.5,1.5)
print_status3(a, 2*a)

Exercise 3 Paste the answer from exercise 2 and add a third argument to your function. What happens if you change the order of the input parameters in the function call?

In [ ]:
# Your code here

Functions that return a value

In addition to receiving values as inputs, functions can also send back values to the person using the function. In computer programming, this is called the "return value".

When you create a function, you can use the return command to specify what value should be sent back to the person using the function. Let's look at an example:

In [ ]:
def my_formula(x):
    y = x**2 + 3
    return y

To "capture" the value returned by the function, you can assign it to a varible like this:

In [ ]:
result = my_formula(3.5)
print(result)

You can also just directly "use" the result of the function if you want:

In [ ]:
print(my_formula(4.6))

Note that as soon as Python sees the return command, it stops running the function, so any code after it will not be executed:

In [ ]:
def myfunction(x):
    print("This gets printed.")
    return x**2 + 3
    print("This does not.")
    
print(myfunction(5))

If you want to send back more than one result to the user of your function, you can separate the results with commas when you use the return command. How do you make use of these two variables that you send back? You will explore this in this exercise:

Exercise 4 Write a function fietwo that takes two real numbers as input and returns the sum and product of the two numbers. Catch the two numbers in two separate variables sum2 and prod2. If you need more help look on the internet, e.g. google "Python function with two variables".

In [ ]:
a=1.5
b=2.5

###
### Your code here
###
In [ ]:
### BEGIN HIDDEN TESTS

question = "answer_1b_4"

to_check = [question + "_%d" % 1]
feedback = ""

key=question + "_%d" % 1

msg = "\nFeedback %s:\n" % key
msg += "="*(len(msg)-2)
msg += "\n\n"

if (float(sum2), float(prod2))==(4.0, 3.75):
    msg+='Answers are correct!'
else:
    msg+='Your answer is incorrect'

print(msg); feedback += msg + "n"

assert True==True

### END HIDDEN TESTS

Importing functions from libraries

One of the big advantages of python is that there are huge collection of libraries that include code for doing a huge number of things for you! We will make extensive use of the library numpy for numerical calculations in Python, and the library matplotlib for generating scientific plots. Beyond this, nearly anything you want to be able to do on a computer can be found in Python libraries, which is one of the reasons it is so popular.

In order make use of these libraries of code, you need to "import" them into the "namespace" of your kernel.

("Namespace" is Python-speak for the list of functions and variable names that you can find in the running copy of Python that is connected to your notebook.)

Here, we will show you a few examples of different ways of importing code into your notebook from a library (also called a "module"). For this, we will take the example we already used above: in the module time, there is a function called sleep() that will do the simple task of "pausing" for a number of seconds given by the its argument.

You can find out more about the time module by looking at its documentation webpage:

https://docs.python.org/3/library/time.html

and specifically about the sleep() function here:

https://docs.python.org/3/library/time.html#time.sleep

Importing a whole module

The simplest way to use the function of the time module is to import it using the following command:

In [ ]:
import time

You can see it has been imported by using the %whos command:

In [ ]:
%whos

Once it has been imported, you can access all the functions of the module by adding time. in front of the function name (from the time module) in your code:

In [ ]:
print("Starting to sleep")
time.sleep(5)
print("Done!")

If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type dir(sleep), which will generate this list.

Sometimes, if you will be using the functions from the module a lot, you can give it a different "prefix" to save yourself some typing:

In [ ]:
import time as tm
print("Starting to sleep")
tm.sleep(5)
print("Done!")

We will use this a lot when using the numpy module, shortening its name to np when we import it, and also for the matplotlib.pyplot submodule, which we will shorten to plt. (These are also typically used conventions in the scientific community.)

Importing a single function

If you need only a single function from a library, there is also a second commonly used way to import only that single function using the following syntax:

In [ ]:
from time import sleep

When you do this, the function sleep() will be available directly in your notebook kernel "namespace" without any prefix:

In [ ]:
print("Starting to sleep")
sleep(5)
print("Done!")

Using %whos, we can now see that we have three different ways to use the sleep() function:

In [ ]:
%whos

If you look around on the internet, you will also find people that will do the following

from numpy import *

This will import all the functions from numpy directly into the namespace of your kernel with no prefix. You might think: what a great idea, this will save me loads of typing!

While true, it will save typing, it also comes with a risk: sometimes different modules have functions that have the same name, but do different things. A concrete example is the function sqrt(), which is available in both the math module and the numpy module. Unfortunately, math.sqrt() will give an error when using numpy arrays (which we will see in the next lecture).

If you import both of them, you will overwrite these functions by the second import, and if you're not careful, you will forget which one you are using, and it could cause your code to break. It will also "crowd" your notebooks namespace: using the whos function, you will suddenly see hundreds or even thousands of functions, instead of only just a module.

For these reasons, it is generally advised not to use import *, and it is considered poor coding practice in modern python.

Shift-Tab for getting help

Like the tab completion we saw in the first notebook, Jupyter also can give you help on functions you have imported from libraries if you type Shift-Tab.

Say I forgot how to use the sleep() function. If I type the word "sleep" and then push Shift-Tab, Jupyter will bring up a help window for that function.

Try it: click on any part of the word sleep in the following code cell and push Shift-Tab:

In [ ]:
sleep

You can also find the same help as the output of a code cell by using the help() function:

In [ ]:
help(sleep)

There are extensive online resources for many modules. The most used modules have helpful examples on the functions and how to implement them.

Exercise 5 Find help for the following functions abs, int, input, and plot from the matplotlib.pyplot library

In [ ]:
# Your code here

Global variables, local variables, and variable scope

In our first functions above, we saw a couple of examples of using variables inside functions.

In the first example, we used the variables a and b inside our function that we created outside our function, directly in our notebook.

In the second example, we used the "temporary" variable x inside our function.

These were two examples of different variable "scope". In computer programming, scope is a set of rules that python uses when it tries to look up the value of a variable.

In the slightly simplified picture we will work with here, variables can have two different types of "scopes": global scope and local scope.

If Python looks for a variable value, it first looks in the local scope (also called "local namespace"). If it does not find it, Python will go up into the global scope (also called the "global namespace") and look for the variable there. If it does not find the variable there, it will trigger an error (a NameError to be precise).

How do I create a global variable? By default, if you create a variable directly in your notebook (and not in a function in your notebook), it will always be global. So, actually, you've already created a bunch of global variables!

Any variables you define inside a function in your code will be a local variable (including the input variables automatically created if your function takes any arguments).

If you want to create a global variable inside a function, or make sure the variable you are referring to is the global variable and not the local one, you can do this by the global qualifier, which we will look at in a minute.

Let's take a look at this in more detail by analyzing a few examples.

Example 1 Accessing a global variable inside a function

In [ ]:
a1 = 5

def my_func():
    print(a1)
    
my_func()
a1 = 6
my_func()

In this example, when Python is inside the function my_func(), it first looks to see if there is a variable a in the local scope of the function. It does not find one, so it then goes and looks in the global scope. There, it finds a variable a, and so it uses this one.

Example 2 An example that doesn't work (unless you've run the next cell, in which case it will only fail again after you restart your kernel)

In [ ]:
def my_func():
    print(b1)
    
my_func()

This code gives a NameError because there is no variable b1 yet created in the global scope. If we run the following code cell and try the code above again, it will work.

In [ ]:
b1 = 6

Here you can see one of risks of languages like python: because of the persistent memory of the kernel, code can succeed or fail depending on what code you have run before it...

If you want to see the error message above again, you can delete variable b1 using this code and run it again:

In [ ]:
del b1

Example 3 Variables defined in the local scope of a function are not accessible outside the function

In [ ]:
def my_func():
    x = 5
    
my_func()

print(x)

Example 4 Variables passed to functions cannot be modified by the function (more on this later when we look at more complicated data structures...sometimes this is different)

In [ ]:
def my_func(a):
    a = 6
    
a=5
my_func(a)
print(a)

This one is a bit subtle (mega-confusing?) because we re-used the same name a for the local variable in the function as the global variable outside of the function. However, the operation is quite logical. When the function code starts running, it creates a local variable a to store the value it received. And now, because there is already a local variable called a, using a in the function refers to the local variable a, not the global variable a we define before calling the function.

Example 5 This one is a tricky one.

In [ ]:
a = 6

def my_func():
    a = 7

print(a)
my_func()
print(a)

It would seem that the function would refer to the global variable a and therefore change it's value. However, it is tricky since we first use a in the function in an assignment. An assignment in python will automatically create a variable if it does not exist, and so python creates a new variable named a in the local scope. The name a inside the function now refers to this newly created local variable, and therefore the global variable will not be changed. In fact, this guarantees that you cannot change global variables inside a function, unless you use the global qualifier shown in the next example.

Example 6 If you want to make sure that the a inside your function is referring to the global variable a, you can include the line global a inside your function to tell Python that you mean the global variable a.

In [ ]:
a = 6

def my_func():
    global a
    a = 7

print(a)
my_func()
print(a)

Summary of the rules for global and local variables:

  • If a local variable of the same name exists or is created by python (by assignment, for example), then python uses the local variable
  • If you try to use a variable name that does not exist locally, python checks for a global variable of the same name
  • If you want to change the value of a global inside a function, then you must use the global statement to make it clear to Python than you want that name to refer to the global variable