Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

7 Code Blocks: Functions

7.1Lesson goals

7.2Overview

A function is a labelled code block that runs when its name is called. When writing longer Python programs, we may notice sections of code that have a similar logical pattern. We can bundle up that code into a function and simply call the function with one line of code. This creates a more manageable code base with fewer lines of duplicated code throughout our program. In this lesson, we will explain how to write functions, how to return the output of a function, how to pass arguments to a function, and explore how variables are scoped between function code blocks.

7.3Creating a function

Creating a simple function is straightforward in Python. Let us look at the example code below that creates the function test():

# Creating the function
def test():
    print("Running test function")

# Calling the function
test()
test()
Running test function
Running test function
print("Including ()")
test()

print("Not including ()")
test
Including ()
Running test function
Not including ()
<function __main__.test()>

In the first case the () tells the interpreter to “call” / execute the function. The second case just references the function object itself → Functions in Python are also objects (just like everything else!) and therefore are part of the function class:

print(type(test))
<class 'function'>

Notice the lack of () when passing test() in the type() function. This is because we are referencing the object itself. We do not want to execute the function.

Create a function called my_name() that prints out the statement,

My name is [NAME].

where [NAME] is your name. After creating the function, demonstrate that it works.


Solution:

This short example demonstrates the basics in creating and using a function. The code block below uses the name of Goldy Gopher when creating my_name().

# Print out name to shell
def my_name():
    print("My name is Goldy Gopher!")

# Call the function
my_name()
My name is Goldy Gopher!

7.4Returning an output

Sometimes we want a function to return a calculated back into the main code block. This is done by using the return statement:

# Define the function
def my_name():    
    name = "Goldy Gopher"
    return name
    
# Call the function
username = my_name()

# Display the output
print(username)
Goldy Gopher

The return name command allows my_name() to return the value that is stored in name to the main code block. We assign this value to the variable username. You can even return multiple outputs using , in both the return statement and when creating variables:

# Define the function
def full_name():    
    first_name = "Goldy"
    last_name = "Gopher"
    return first_name, last_name

# Call the function   
user_first_name, user_last_name = full_name()

# Display the outputs
print(user_first_name)
print(user_last_name)
Goldy
Gopher

7.5Arguments

A variable that is passed into a function is called an argument. Arguments allow us to pass data into a function. Let’s see how passing arguments work with a simple function that converts a temperature in Fahrenheit to Celsius:

def convert_f_to_c(temp):    
    celsius = (temp - 32) * (5/9)
    return celsius
    
temp_c = convert_f_to_c(5)  # notice the `5` inside the ()
print("Temperature in C:", temp_c)
Temperature in C: -15.0

We define the required argument temp inside the ( ) symbols when we define the function convert_f_to_c(). We later pass a value into the function (in this case 5 for 5 °F) when calling the function.

We can even pass multiple arguments into a function:

def full_name(first, last):    
    first_name = first
    last_name = last
    return first_name, last_name
    
user_first_name, user_last_name = full_name("Goldy", "Gopher")
print(user_first_name)
print(user_last_name)
Goldy
Gopher

7.5.1Example: Temperature conversion

Create a code block that converts the temperatures,

68 oF,319 oF,150.2 oF,and 901.4 oF68 \text{ }^\text{o} \text{F}, -319 \text{ }^\text{o} \text{F}, 150.2 \text{ }^\text{o} \text{F}, \text{and } 901.4 \text{ }^\text{o} \text{F}

from Fahrenheit to Celsius using the function convert_f_to_c() from above. Use the following guidelines and tips when creating your code:

Solution:

This example demonstrates how our increasing knowledge in Python allows us to adapt previously made code for new applications. Since convert_f_to_c() only accepts single values as an input (e.g., an int or float object) we utilize a for loop to access each temperature one at a time. An example solution is shown below:

# Convert Fahrenheit to Celsius
def convert_f_to_c(temp):    
    celsius = (temp - 32) * (5/9)
    return celsius

# List of 
temp_f = [68, -319, 150.2, 901.4]

# Initialize converted list
temp_c = []

# Loop over all entries in temp_f
for i in temp_f:
    celsius_temp = round(convert_f_to_c(i),1)
    temp_c.append(celsius_temp)

# Display converted temperatures
print("Temp in C:", temp_c)
Temp in C: [20.0, -195.0, 65.7, 483.0]

There are two common types of arguments in Python: positional arguments and keyword arguments. Let’s go over both.

Positional arguments are arguments that are passed into a function in a specific order. An error is flagged if an incorrect number of arguments are passed, which is shown in the example function below:

Furthermore, an incorrect argument order can lead to odd results,

user_first_name, user_last_name, user_age = user("Goldy", 15, "Gopher")
print("User's first name:", user_first_name)
print("User's last name:", user_last_name)
print("User's age in months:", user_age)
User's first name: Goldy
User's last name: 15
User's age in months: GopherGopherGopherGopherGopherGopherGopherGopherGopherGopherGopherGopher

Position matters when using positional arguments!

7.5.3Keyword arguments

keyword arguments are arguments that are assigned with an identifying keyword. This allows us to explicitly state what each argument represents:

user_first_name, user_last_name, user_age = user(first="Goldy", last="Gopher", 
                                                 age=15)
print("User's first name:", user_first_name)
print("User's last name:", user_last_name)
print("User's age in months:", user_age)
User's first name: Goldy
User's last name: Gopher
User's age in months: 180

Notice that when the function is called each argument has both and keyword and value (e.g., first="Goldy"). The command style explicitly tells the Python interpreter the link between value and keyword.

What is nice about keyword arguments is that the order of the arguments does not matter any more:

user_first_name, user_last_name, user_age = user(first="Goldy", age=15,
                                                last="Gopher")
print("User's first name:", user_first_name)
print("User's last name:", user_last_name)
print("User's age in months:", user_age)
User's first name: Goldy
User's last name: Gopher
User's age in months: 180

Keyword arguments increase code readability at the expense of more typing.

7.6Common errors when passing arguments

There are a few common errors to watch out for when passing arguments into functions.

7.6.1Passing keyword arguments before positional arguments

If we put keyword arguments before positional arguments we will see the following error:

user_first_name, user_last_name, user_age = user(first="Goldy", "Gopher", age=15)
  Cell In[14], line 1
    user_first_name, user_last_name, user_age = user(first="Goldy", "Gopher", age=15)
                                                                                    ^
SyntaxError: positional argument follows keyword argument

The error SyntaxError: positional argument follows keyword argument is reporting that we tried to pass a positional argument after a keyword argument, which we cannot do. Positional arguments must come first before keyword arguments. The interpreter does not know with 100 % certainty the meaning of the positional arguments if keyword arguments are first declared.

7.6.2Passing too many arguments

A similar error will occur if we try to pass too many arguments into a single variable. An example of this is shown below:

user_first_name, user_last_name, user_age = user("Goldy", "Gopher",
                                                 first="Goldie", age=15)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[15], line 1
----> 1 user_first_name, user_last_name, user_age = user("Goldy", "Gopher",
      2                                                  first="Goldie", age=15)

TypeError: user() got multiple values for argument 'first'

Here, the error TypeError: user() got multiple values for argument 'first' is reporting that we already passed a value to first with the first positional argument, so passing first="Goldie" afterwards caused an error. The interpreter does not know which value: "Goldy" or "Goldie" should be used for first.

7.6.3Too many positional arguments

Likewise, if you pass more arguments than what a function is expecting, Python will report an error:

user_first_name, user_last_name, user_age = user("Goldy", "Gopher", 15, 20)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 user_first_name, user_last_name, user_age = user("Goldy", "Gopher", 15, 20)

TypeError: user() takes 3 positional arguments but 4 were given

The error TypeError: user() takes 3 positional arguments but 4 were given reports that user()only accepts three arguments (i.e., first, last, and age), but the function received four arguments. The interpreter does not know what to do with the extra argument.

7.7Assigning default values to arguments

Sometimes it is useful to assign a default value to an argument if that value is commonly used. An added benefit to doing this is that these arguments no longer need to be assigned during every function call.

The code below demonstrates this concept using a function that converts a temperature from one of the four common temperature scales (Celsius, Fahrenheit, Kelvin, or Rankine) to another temperature scale:

def convert_temp(temp, input_unit="F", output_unit="K"):
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit

As seen above, our function takes in three arguments: temp, input_unit, and output_unit. Notice that the arguments input_unit and output_unit are assigned values in the def line by using the keyword argument notation from earlier (i.e., input_unit="F" and output_unit="K"). This allows us to assign default values to these arguments. The argument temp has not been assigned a default value though. Therefore, any time we want to call convert_temp(), we will need to provide an argument for temp. This can be done using a positional argument, like shown in the example above, or using a keyword argument:

temp, unit = convert_temp(temp=10)
print(f"Temp in {unit}: {round(temp,1)}")
Temp in K: 260.9

So the interpreter uses the default values for input_unit and output_unit ("F" and "K", respectively) since we did not explicitly define them. Furthermore, we can still use positional arguments since temp is the only argument without a default value:

temp, unit = convert_temp(5)
print(f"Temp in {unit}: {round(temp,1)}")
Temp in K: 258.1

Even though we have defined default values to both input_unit and output_unit, we can override them during a function call by either using a positional or keyword argument:

temp, unit = convert_temp(10, "F", "C")
print(f"Temp in {unit}: {round(temp,1)}")

temp, unit = convert_temp(temp=10, input_unit="F", output_unit="C")
print(f"Temp in {unit}: {round(temp,1)}")
Temp in C: -12.2
Temp in C: -12.2

In the first example we override the default values for both input_unit and output_unit using positional arguments and the second examples does the same thing but with keyword arguments.

There are limits to how the Python interpreter will accept a mixed ordering of default and non-default arguments. For example, all arguments that do NOT have a default value must be listed prior to the arguments that will have a default value. This requirement in Python prevents possible positional argument errors from originating during later function calls as the shell will not be able to link a positional argument to the appropriate non-default valued argument. The example below shows what will happen if you fail to do this:

def convert_temp(temp=10, input_unit="F", output_unit):
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit

temp, unit = convert_temp("C")
print(f"Temp in {unit:}:", round(temp,1))
  Cell In[21], line 1
    def convert_temp(temp=10, input_unit="F", output_unit):
                                              ^
SyntaxError: parameter without a default follows parameter with a default

Here, the error SyntaxError: parameter without a default follows parameter with a default occurring in Line 1 (i.e., during the creation of the function using the def keyword) indicates that an argument without a default value is being listed prior to an argument that has a default value.

A simple fix to this error is to switch the order of the arguments in the def line so that output_unit (i.e., the argument without a default value) is listed first:

def convert_temp(output_unit, temp=10, input_unit="F"):
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit

temp, unit = convert_temp("C")
print(f"Temp in {unit:}:", round(temp,1))
Temp in C:
 -12.2

Now the function works according to plan!

7.8Variable scoping in code blocks

A typical Python program will use many variables across many code blocks, so it is important to keep in mind the scope of a variable’s name across blocks of code. The scope represents how visible a variable’s name is seen throughout the code. Depending on how a variable is defined, the scope of a variable may be visible throughout the entire code or only within a small code block. This is demonstrated in the follow example:

num = 1                     #### Start code block 1, global scope

def multi_twenty(num):         #### Start code block 2, local scope    
    # multiply num by 20          # local scope
    print("local num:", num)      #
    print("id num:", id(num))     #
    num = num * 20                # local scope
    return num                 #### End of code block 2, local scope   

big_num = multi_twenty(5)      #  
print("Big num", big_num)      #  
print("global num:", num)      #
print("id num:", id(num))   #### End of code block 1, global scope
local num: 5
id num: 11278056
Big num 100
global num: 1
id num: 11277928

In this example, we assign a variable named num in two places. We first assign num the value 1 in code block 1, and then assign num a different value (via a recursive value of num * 20) in the function multi_twenty, which is part of code block two. Each of these code blocks have a different variable scope for num. Code block one, has a global variable scope as it is the “outermost” code block. Code block two, which is an “inner” code block, has a local variable scope inside the code block. When we call multi_twenty(5), we assign 5 to num in the function, which is technically a different variable named num from the globally scoped num variable in the first line of code. This is proven using the function id() that shows each num variable is referencing two different memory locations.

It is important to note that variables assigned in a higher scope can be read in a local scope. The example below demonstrates with the globally scoped variable C_RATIO:

# global scope 
C_RATIO = 5/9

# code block scope (functional scope)
def convert_f_to_c(temp):    
    celsius = (temp - 32) * C_RATIO
    return celsius
    
temp_c = convert_f_to_c(5)
print("Temp in C:", temp_c)
Temp in C: -15.0

Here, we see that C_RATIO is read inside the function convert_f_to_c() even though it was not passed as an argument. If we created a new variable called C_RATIO inside of convert_f_to_c(), this new variable would have been used instead of the original value (i.e., see the previous example using our multi_twenty() function). All in all, failure to keep the scope of variables in mind for your code can lead to unintended consequences.

7.9Final thoughts

An important “Pythonic” principle is to avoid having duplicated code throughout a program. With a function, we define a reusable code block that accepts input arguments and returns an output. From here, we can call this code block many times throughout a program and issue different arguments to this code block without having to rewrite code. Functions can have variables passed into them using both positional arguments and keyword arguments, and we can also define default values for arguments. Each function’s code block has its own variable scope in which it operates, which is important to track in order to prevent unintended behavior from a program.