Skip to content
Snippets Groups Projects
Notebook 2 Functions.md 23.75 KiB
jupyter:
  jupytext:
    formats: ipynb,md
    text_representation:
      extension: .md
      format_name: markdown
      format_version: '1.1'
      jupytext_version: 1.2.2
  kernelspec:
    display_name: Python 3
    language: python
    name: python3

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 (ie. give an error). Or even worse, and even harder to debug the mistake may may give an error message but give you the wrong 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:

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)

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. The 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 IdentationError.

In notebooks, you can also select a line, or multiple lines, and then use Tab to increase their indentation level, or use Shift-Tab to decrease their indentation level.

def test_tab_and_shift_tab():
    some code
        that is indendented
    try selecting this text
    and then pushing tab 
    and shift-tab

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:

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 2.1 Write your own function that contains two lines of code. The first line should make a new variable var2 that is var converted to an integer. The second line of your code should print the value of var2.

Using this code, play around with the indentation (add extra tabs and spaces for example) to see how 'critical' Python is with indentation. For example: does three spaces work instead of Tab? Does one space work? What about Tab on the first line and three spaces on the second line? Can you make Python trigger an IdentationError?

var=3.5

# Your function here

Functions with input variables

Let's say that we wanted to print out the status of variables that we do not know the name of ahead of time, as in the example above. Say we wanted to make a function that could print out a message with the status of 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:

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.2 Copy your code from exercise 2.1 (but then with just normal indentation using tabs) into the cell below and change it such that it uses a function with input parameters to achieve the same task.

# 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 "arguements), or "input parameters" (or just "parameters").

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 2.3 Make a new function print_status4() that takes three variables as arguments and prints out messages telling the user the values of each of them (as above, but with three input variables). Test it to make sure it works.

# 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:

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:

result = my_formula(3.5)
print(result)

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

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:

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 2.4 (a) Write a function that takes two real numbers as input and returns the sum and product of the two numbers. In your function, try to send both of the calculated numbers back as a return value. We have not taught you that yet so you will have to look it up: I recommend trying a google search for "python function return two variables".

# Your function here
def product_and_sum(...):
    ...

(b) Now USE your function to calculate the sum and product of a and b, "capturing" the sum and product in variables s and p:

a=1.5
b=2.5

...some code that uses the return value of your function to set variable s and p...

print("Sum is:", s)
print("Product is:", p)

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 used already in Notebook 1: in the module time, there is a function called sleep() that will perform the 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 be able use the sleep function of the time module is to import it using the following command:

import time

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

%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:

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:

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:

from time import sleep

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

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:

%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! Instead of typing np.sqrt() for example, to use the square-root function, I could just type sqrt().

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 learn more about in later notebooks).

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:

sleep

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

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 2.5 (a) Find help for the built-in functions abs, int, and input. Which of the help functions are easy to read? Which one does not provide such useful information (compared to the online documentation page)? (Put each help command in a separate cell)

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

(b) Import the function glob from the library glob and print its help information. What does the function glob("../*") do?

# run the help here
# your code here