7 Code Blocks: Functions
7.1Lesson goals¶
Create functions for more efficient and easier to read code.
Return values from a function into the main code block.
Understand how arguments are passed into functions.
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
The second line has the command def test(): which is the function declaration statement that starts the code block.
The keyword def stands for “define”, as in we are
defining our function, and test is the name we give to the function. Function declarations require two parenthesis
() after the function name. Finally, there is the colon : which ends the function declaration statement. The
subsequent indented code contains the code block that the function will represent. In this case, the function will
print out the string running test function to the terminal. Notice that the : also implies that the code block
for the function needs to be indented. This follows a similar coding pattern to what we saw with
conditional and looping statements.
We then called the function by typing test(). The parentheses () after test means we are calling the function and
is key to execute the function’s code block. If we do not include (), the function will not be properly called:
print("Including ()")
test()
print("Not including ()")
testIncluding ()
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.
7.3.1Example: Insert name here¶
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,
from Fahrenheit to Celsius using the function convert_f_to_c() from above. Use the following guidelines and tips when
creating your code:
Create a
listcalledtemp_fthat stores the starting temperatures.In its current state,
convert_f_to_c()will not accept alistas an argument. If you do this, you will get an error. Instead, use aforloop to pass each value one at a time into the function.Store your converted temperatures into a new
listobject calledtemp_c. You will need to first initialize this object without any value.Round converted temperatures to the tenths position.
Converted temperatures can be added to
temp_cusing the.append()method for lists. Look online for examples on how to use this useful method!
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]
7.5.2Positional arguments¶
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:
def user(first, last, age):
first_name = first
last_name = last
months_old = age * 12
return first_name, last_name, months_old
print("Correct order of positional arguments")
user_first_name, user_last_name, user_age = user("Goldy", "Gopher", 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)
print("\n")
print("Forgot age argument")
user_first_name, user_last_name, user_age = user("Goldy", "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)Correct order of positional arguments
User's first name: Goldy
User's last name: Gopher
User's age in months: 180
Forgot age argument
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[10], line 16
12
13 print("\n")
14
15 print("Forgot age argument")
---> 16 user_first_name, user_last_name, user_age = user("Goldy", "Gopher")
17 print("User's first name:", user_first_name)
18 print("User's last name:", user_last_name)
19 print("User's age in months:", user_age)
TypeError: user() missing 1 required positional argument: 'age'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 givenThe 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_unitAs 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 scopelocal 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.