2. Functions#
Python has a number of built-in functions for things like math calculations, working with the filesystem, and reading in common file formats. First, we’ll talk about how to use modules to access more functions. Then, we’ll see how we can define our own custom functions to do whatever we want.
2.1. Modules#
In addition to the basic Python syntax and commands, more tools can be accessed using modules. Modules must be imported into your current namespace using the import statement.
For example, the math module has additional math functions that go beyond just simple arithmetic. We have to first import it to use it.
import math
memory_delay_days = 3.3
memory_delay_ceil = math.ceil(memory_delay_days)
print(memory_delay_ceil)
4
Use the help function to see more information about the math module.
There are multiple ways to import things from modules that give you some flexibility. You can import a specific function or functions from a module.
from math import ceil, floor
print(floor(memory_delay_days))
3
Usually it’s best to just import the module and then call functions using dot syntax, like math.ceil. That way, it’s easy to tell where a given function came from.
You can give a module a different name when importing it. This is often a good idea when the module name is long.
import math as mt
print(mt.exp(.5))
1.6487212707001282
There are conventions for short names to use for some modules. For the math module, most people don’t rename it, because it’s already pretty short. Later on, we’ll see how people usually import the Numeric Python (NumPy) module using import numpy as np to make commands easier to type, like np.mean.
Exercise: modules#
Use the math module to calculate \(cos(\pi)\). Run help(math) to see a summary of the functions and variables available. See the DATA section at the end, which defines some standard mathematical constants.
# answer here
2.2. Functions#
A key method for running programs involves defining a function that runs some code to complete some purpose. This function can then be used whenever you need to complete that task.
output1, output2, ... = function_name(input1, input2, ...)
Functions help you follow the DRY principle: Don’t Repeat Yourself. Beginners often do a lot of copying and pasting code. But this can lead to programs that are harder to read, debug, and improve, because they have a lot of redundancy.
Usually, functions have two parts: inputs (also known as parameters) and outputs (also known as return values). Let’s look the documentation for a simple function. The math.ceil function takes one input and returns one output.
help(math.ceil)
Help on built-in function ceil in module math:
ceil(x, /)
Return the ceiling of x as an Integral.
This is the smallest integer >= x.
When we call math.ceil, we input one value and get one value as output.
math.ceil(3.1)
4
We can define a new function of our own using the def keyword.
def function_name(input1, input2):
# code to calculate some output
return output1, output2
The return statement determines the output(s) of the function.
Let’s make a really simple function that takes in a number and doubles it.
def double(x):
return x * 2
Now that we’ve defined this function, we can call it the same as any built-in Python function.
x = 2
y = double(x)
print(y)
4
The x in the definition of the double function is just a name that’s used in the function. When we’re calling the function, we can name the variable whatever we want…
z = 10
print(double(z))
20
Or not give it a name at all and just input a number directly into the function without assigning it to a variable first.
print(double(9000))
18000
Variables defined in a function can only be accessed in the function. A function has a scope where variables are defined locally. They’re temporary variables used when executing the function. After the function runs, they don’t exist anymore.
def test_variables(a):
b = a * 2
c = b ** 2
return c + 7
a = 2
d = test_variables(a)
print(d)
# print(c) throws an error; c is not defined out here (try it!)
23
We can use functions to repeat things we need to do multiple times. Say that we have data for multiple participants, and we want to be able to print a summary for a given participant.
# data for each participant
data1 = {"id": "001", "accuracy": 0.82, "response_time": 4.2}
data2 = {"id": "002", "accuracy": 0.53, "response_time": 3.2}
We can use a function to make this convenient. Instead of having to copy and paste the printing code whenever we want to use it, we just call the function.
def summarize(data):
"""Summarize participant data."""
percent = round(data["accuracy"] * 100)
print(f"sub-{data["id"]}: {percent}%, {data["response_time"]}s")
summarize(data1)
summarize(data2)
sub-001: 82%, 4.2s
sub-002: 53%, 3.2s
Exercise: functions#
Create a function called square that will take in some variable \(x\) and return \(x^2\). In Python, you can square a variable using x ** 2. After defining your function, call it to calculate the squares of 2, 3, and 4.
Create another function called add_three that takes in three variables \(x\), \(y\), and \(z\) and returns \(x + y + z\). Use it to add 2, 3, and 4 together.
# answer here
2.3. Docstrings#
A function definition can include a special string known as a docstring, which documents how the function should be called and what values it will return.
The docstring is written right at the start of the function definition. Docstrings are usually specified using triple quotes, like """This is a docstring.""" The triple quotes allow you to split a string over multiple lines, so you can explain what the inputs and outputs are. But often a simple function will just have one line saying what it does.
def print_date():
"""Display the current date."""
from datetime import date
print(date.today())
print_date()
2026-02-14
The first line of a docstring should be written as an imperative sentence. That is, say what the function will do. Good: Display the current date.. Bad: This function displays the current date.. Notice that the second version takes more words to say the same thing.
We can see a function’s docstring by using help, or by typing function_name? in Jupyter.
help(print_date)
Help on function print_date in module __main__:
print_date()
Display the current date.
print_date?
Sometimes, it’s a good idea to give more detailed documentation about a function. To do this, we can add more lines to our docstring. The first line should still be a one-sentence description of the main purpose, and it should be followed by a blank line before any additional text.
def print_date():
"""
Display the current date.
The date will be displayed in YYYY-MM-DD format.
"""
from datetime import date
print(date.today())
Now, when we use help to remind ourselves about what the function does, we’ll know what formatting to expect.
help(print_date)
Help on function print_date in module __main__:
print_date()
Display the current date.
The date will be displayed in YYYY-MM-DD format.
Python has docstrings for all its standard functions. For example, we can use help to learn more about the math.cos function.
help(math.cos)
Help on built-in function cos in module math:
cos(x, /)
Return the cosine of x (measured in radians).
Exercise: docstrings#
Write a function called format_name that takes first_name and last_name inputs and returns a string formatted like "last_name, first_name". For example, running format_name("Jane", "Smith") should return the string "Smith, Jane".
Add a one-line docstring that describes the purpose of the function in a short, imperative sentence.
Expand your docstring, adding a blank line, then a line describing the output of your function.
# answer here
2.4. Multiple inputs and outputs#
Functions can take in multiple inputs and return multiple outputs. Both inputs and outputs are stored in tuples, which we covered briefly before.
def return_two_things():
"""Return two variables in a tuple."""
return 1, 2
t = return_two_things()
print(t)
print(type(t))
(1, 2)
<class 'tuple'>
Usually, we’ll want to assign multiple outputs to different variables. We can do this using a feature called tuple unpacking.
a, b = return_two_things()
print(a)
print(b)
1
2
This is mostly used when calling a function, but it works with any tuple. Sometimes people will use it to define multiple variables in one line.
a, b, c = 1, 2, 3
print(a)
print(b)
print(c)
1
2
3
Let’s look at another example with multiple inputs and multiple outputs.
def double_double(x, y):
"""Multiply two numbers by two."""
return x * 2, y * 2
If we call this function, we’ll get two values back.
x = 2
y = 4
a, b = double_double(x, y)
print(a, b)
4 8
Exercise: multiple inputs and outputs#
Write a function called two_powers that takes three variables, x, y, and z, and returns two values: \(x^y\) and \(x^z\). Test it with \(x=2\), \(y=3\), and \(z=4\).
Write a docstring explaining how your function works. The first line should be a one-sentence summary. The second line should be blank. After the second line, add a longer explanation of how the two output values are calculated. The goal is to make it so someone could tell what the function does without having to read the source code, because that makes it easier to work with libraries of functions.
# answer here
2.5. Function arguments#
There are two ways to pass inputs, or arguments, to a function: as positional arguments or keyword arguments. Positional arguments are identified based on the order in which they are given to the function, while keyword arguments are identified based on parameter names.
For example, we have previously used the round function with positional arguments only.
round(3.14159, 2)
3.14
The round function knows that the first argument is the number, and the second argument is the number of digits to round to.
We can also use keyword arguments, which specify a name. Function docstrings show the names of their parameters.
help(round)
Help on built-in function round in module builtins:
round(number, ndigits=None)
Round a number to a given precision in decimal digits.
The return value is an integer if ndigits is omitted or None. Otherwise
the return value has the same type as the number. ndigits may be negative.
When we call the round function, we can either use positional arguments, keyword arguments, or a mix of the two.
print(round(3.14159, 2))
print(round(3.14159, ndigits=2))
print(round(number=3.14159, ndigits=2))
3.14
3.14
3.14
The round function has a default value for ndigits, which is None. As the docstring says, this means that it will round to the nearest integer by default.
round(3.14159)
3
Default values are specified using parameter=default_value in the function definition.
def myfunction(pos1, pos2, pos3, kw1="default", kw2=42):
...
In this example, there are three required parameters and two optional parameters, which will be set to "default" and 42 if they are not specified.
Let’s use a keyword parameter to set a default value for a function. We’ll multiply an input by 3 by default.
def mult_three_default(x, mult=3):
return x * mult
print(mult_three_default(4))
12
We can also change the default value that we multiply by, in two ways. We can either specify it by just putting a value in the second position when calling the function:
print(mult_three_default(4, 100))
400
Or we can specify the keyword explicitly:
print(mult_three_default(4, mult=100))
400
If we have multiple keywords, we can just specify one, and the other one will use the default.
def prod(x=2, y=3):
return x * y
print(prod())
print(prod(y=6))
6
12
This allows functions to have many options with defaults, allowing users to change some settings without having to specify all of them.
Exercise: function arguments#
Define two variables: c = 1 and d = 2. Run print(c, d) and see what it prints. The print function has an option that determines how multiple variables are separated in the output. Run help(print) to see the documentation. Run print again, but this time set the sep keyword so that there is a comma between the arguments instead of a space.
# answer here
2.6. Methods#
Some functions are meant to be applied to specific classes of variables. These functions are called methods.
For example, there are methods for working with strings, such as split.
subject_code = "sub-001"
string_parts = subject_code.split("-") # split into parts divided by -
number_code = string_parts[1] # get the second part
To see all the methods available for an object, use the help function and pass in any object of that class.
# help("") # show help for the string class
# help(str) # can also use the name of the class
# help([]) # show help for the list class
# help(list) # this works also
To call a method, write the variable, a dot (.), and then the name of the method. For example, if we want to make a string from different elements of a list, separating the elements by a dash (-):
parts = ["sub", "002"]
separator = "-"
subject_code = separator.join(parts)
print(subject_code)
sub-002
To use the join function, we write the separator we want to use (here, "-"), a dot (.), the name of the function (join), and the input to that function in parentheses, which here is a list of strings we want to join together.
Exercise: methods#
Create a list called a with the numbers 1, 2, 3.
Use the help function to display documentation for the append method of the list class. If you run help(a), you will see all the help documentation for lists. If you run help(a.append), you will see just the documentation for the append method.
Use the append method to add the number 4 to the end of your list.
Read the help documentation for the pop method of lists. What does that do? Try running it. How did it change your list?
# answer here
2.7. If statements#
Sometimes, we need to decide whether to run a command or not, depending on the conditions. To do this, we can write an if statement:
if some_condition:
code_to_run_if_true
The simplest form of an if statement is to run some code, only if a condition is satisfied. For example, say if we want to check if one variable is larger than another and run some code only if it is.
a = 5
b = 8
if a > b:
print("a is greater than b.")
Because \(a < b\), the code is not executed.
We could instead check if the opposite is true.
if b > a:
print("b is greater than a.")
b is greater than a.
Here, the code checks if \(b>a\), finds that it is, and runs the code to print our message.
We can optionally also include an else block at the end. The code here will run if the condition is not true.
if b > a:
print("b is greater than a.")
else:
print("b is not greater than a.")
b is greater than a.
Exercise: if statements#
Write a function called issmall that takes in a positive number and uses an if statement to test if it is less than 10. It should return True if the integer is less than 10 and return False otherwise. Test it with inputs 1 and 10.
Advanced#
Write a function that works the same way, without using an if statement.
# answer here
2.8. If/elif/else statements#
We can put multiple checks together using an if/elif statement. The elif keyword is short for “else if”. Different conditions are checked in order. If the first condition is true, then the code in that block will be executed. If the first condition is false, then the elif condition will be checked. You can have as many elif conditions as you want.
a = 5
b = 8
if a > b:
print("a is greater than b.")
elif b > a:
print("b is greater than a.")
b is greater than a.
Finally, we can optionally also have an else block at the end. This will run code only if none of the other conditions were met.
a = 5
b = 5
if a > b:
print("a is greater than b.")
elif b > a:
print("b is greater than a.")
else:
print("a is equal to b.")
a is equal to b.
To summarize, the syntax for if/elif/else statements is:
if condition:
code_to_run
elif other_condition:
different_code
else:
code_if_neither_condition_true
The various parts of an if/elif/else statement can take any expression that returns a boolean value (that is, True or False). You can get a boolean using tests of equality (using == to test if two variables have the same value, or != to test if they have different values) or inequalities (<, >, <=, >=).
number = 4
string = "name"
print(number < 4) # less than four
print(number <= 4) # less than or equal to four
print(string == "name") # equal to "name"
print(string != "name") # not equal to "name"
False
True
True
False
Exercise: if/elif/else statements#
Define a function called greet prints out a greeting. It should take one input called user. If the user is "Mark", print "Hi, Mark S.!. If the user is "Helena", print "Hello, Helly R."
Advanced#
Add an else block so that, if the user is anyone else, the function will use an f-string to print "Greetings, [user]."
# answer here
2.9. Combining conditionals#
Sometimes we may want to combine tests to check for a more complicated set of conditions. We can combine tests using the keywords and and or.
The and keyword checks if two statements are both True.
print(1 == 1 and 2 > 1) # both parts are True, so this returns True
print(1 == 1 and 2 < 1) # the first part is True, but the second is False
True
False
The or keyword checks if either statement is True, or if both are True.
print(1 == 0 or 2 > 1) # the second part is True, so this returns True
print(1 == 0 or 2 < 1) # both parts are False, so this returns False
True
False
Finally, we can also use the keyword not to get the opposite of some conditional. Sometimes it is easier to check if something does not meet some condition than to check if it does.
For example, sometimes a variable will be initialized to None to indicate that it is undefined. We can check this using not.
def process_condition_code(condition):
if condition is not None:
print(f"Current condition is {condition}.")
else:
print("Current condition is undefined.")
process_condition_code(1)
process_condition_code(None)
Current condition is 1.
Current condition is undefined.
Let’s look at a common example of combining conditionals. For example, say we want to test if a number is between 5 and 10 (inclusive). We’ll write a function to run this check and try it with some different numbers.
def check_in_range(x, lower=5, upper=10):
return x >= lower and x <= upper
print(check_in_range(4))
print(check_in_range(6))
print(check_in_range(11))
False
True
False
Note that we used keyword arguments for our function, so that we can change the range from the default if we want.
print(check_in_range(0.5, lower=0, upper=1))
print(check_in_range(1.5, lower=0, upper=1))
True
False
Note that we could have also written the same thing using if statements.
def check_in_range2(x, lower=5, upper=10):
if x >= lower:
if x <= upper:
return True
else:
return False
else:
return False
print(check_in_range(4))
print(check_in_range(6))
print(check_in_range(11))
False
True
False
This also works, but is harder to read. In this case, it’s better to combine your tests into one statement, x >= lower and x <= upper.
Exercise: combining conditionals#
Write a function called out_of_range that takes in a number x and checks if it is not between 0 and 1. If \(0 \ge x \ge 1\), it should return False; otherwise, it should return True.
Advanced#
Add keyword arguments for lower and upper bounds, with 0 and 1 as the defaults. Change your function to use those bounds instead. Having variables like 0 and 1 specified directly in the code is known as hard coding. By having these be arguments to the function instead, your code becomes more flexible and less “hard coded”.
# answer here
2.10. Summary#
Functions are very important in Python. They allow us to write a piece of code once and then reuse it over and over again.
A function with positional arguments and keyword arguments looks something like this:
def my_function(pos1, pos2, kw1=1, kw2=None):
"""Perform some function."""
# do something with required arguments pos1 and pos2,
# plus optional arguments kw1 and kw2.
We can use if/elif/else statements to run different code depending on variables. This is open helpful within functions, to make them adapt based on the arguments to the function.
We can use if by itself:
if a > 5:
print("a is greater than 5.")
or with combinations of elif and else statements:
if a > 5:
print("a is greater than 5.")
elif a > 0 and a <= 5:
print("a is between 0 and 5.")
else:
print("a is less than or equal to 0.")
With functions and if/elif/else statements, we can write code that will run differently in different situations.
Use the def keyword to define a function, and use if, elif, and else keywords to define conditional statements.
In conditional statements, you can use comparison operators (==, !=, <, >, <=, >=) and combination operators (and, or, not) to test various conditions.