Functions and Conditionals#

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.

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 number 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

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.

We can make a new function using the def keyword.

def function_name(input1, input2):
    # code to calculate some output
    return output1, output2

Usually, functions have two parts: inputs (also known as arguments) and outputs. 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 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

A function can include a special string known as a docstring, which comes right at the start of the function definition. This is 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()
2025-11-29

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).

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 calculate \(x^2\). In Python, you can square a variable using x ** 2. Use the function 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.

Add docstrings to your square and add_three functions. Use help to display the docstring for each function.

# answer here

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

Function arguments#

There are two types of arguments to functions: positional arguments, like we’ve used so far; and keyword arguments, which have names attached to them. Keyword arguments make it so we can label arguments when we’re defining a function. This can sometimes make it easier to see what we’re trying to do when calling a function. It also means that we don’t have to specify all the inputs; we can leave some as their default values instead.

def myfunction(pos1, pos2, pos3, kw1="default", kw2=42):
    ...

In this example, there are three positional arguments, which are required, and two optional keyword arguments, which default to "default" and 42 if they are not specified.

Let’s use a keyword argument 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

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

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

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)
print(number <= 4)
print(string == "name")
print(string != "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

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

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.