Lesson 8: Functions#

This lesson is modified from functions from geo-python.

Binder


Objectives#

By the end of this lesson, you will be able to

  • explain the importance of using functions in your code

  • create and call a function

  • pass multiple inputs and return multipe outputs of different types to a function

  • explain the difference between global and local namespace with respect to names of variables (opitional)


1. Functions#

A functions is a block of code for a specific task that you can re-use in your programs (this lesson). A module is a collection of functions (next lesson). In this lesson, we will learn how to create and use functions, one of the most powerful concepts in programming.

1.1 Why do we use functions?#

A function is a block of organized, reusable code that can make your programs more effective, easier to read, and simple to manage. You can think functions as little self-contained programs that can perform a specific task that you can use repeatedly in your code.

One of the basic principles in good programming is “do not repeat yourself”. In other words, you should avoid having duplicate lines of code in your scripts. Functions are a good way to avoid such situations and they can save you a lot of time and effort as you don’t need to tell the computer repeatedly what to do every time it does a common task, such as converting temperatures from Fahrenheit to Celsius.

During the course we have already used some functions such as the print() , min(), range() and many other built-in functions in Python.

Take the min() function as an example. Imagine every time you need to find the minimum of a list, you write a code that looks like that?

numbers = [5, 3, 8, 2, 7]

# Initialize the minimum_value with the first element of the list
minimum_value = numbers[0]

# Iterate through the list to find the minimum value
for number in numbers[1:]:
    if number < minimum_value:
        minimum_value = number
print(f"The minimum value in the list is: {minimum_value}")
The minimum value in the list is: 2

Would not it be easier to define a function and call it when we need to find the minimum of a list? Here is an example.

def minimum(numbers):
    # Check if the list is not empty
    if not numbers:
        return None  # Return None if the list is empty

    # Initialize the minimum_value with the first element of the list
    minimum_value = numbers[0]

    # Iterate through the list to find the minimum value
    for number in numbers[1:]:
        if number < minimum_value:
            minimum_value = number

    return minimum_value
# Example usage:
numbers = [5, 3, 8, 2, 7]
minimum_value = minimum(numbers)
print(f"The minimum value in the list is: {minimum_value}")
The minimum value in the list is: 2

Here are few advantages of doing this:

  • Functions allow you to abstract away complex operations. Abstraction makes it easier to reason about your code such that the users of the function only need to know what it does, not necessarily how it achieves it.

  • Functions can accept parameters, allowing them to be more flexible and adaptable to different situations.

  • Once you’ve written a function to perform a specific task, you can reuse that function whenever you need to perform that task again.

Here you will learn to to create and use functions

1.2 Anatomy of a function#

Let’s consider the task from the first lesson when we converted temperatures from Celsius to Fahrenheit. Such an operation is a fairly common task when dealing with temperature datasets. Thus we might need to repeat such calculations quite often when analysing or comparing weather or climate data between the US and Europe, for example.

1.2.1 Our first function#

Let’s define our first function called celsius_to_fahr.

def celsius_to_fahr(temp):
    return 9 / 5 * temp + 32

image.png

Anatomy of a function (Source: GeoPython)

The function definition opens with the keyword def followed by the name of the function and a list of parameter names in parentheses. The body of the function, the statements that are executed when it runs, is indented below the definition line just like for for loops or conditional statements.

When we call the function, the values we pass to it are assigned to the corresponding parameter variables so that we can use them inside the function (e.g., the variable temp in this function example). Inside the function, we use a return statement to define the value that should be given back when the function is used, or called.

Defining a function does nothing other than make it available for use in our notebooks. In order to use the function we need to call it.

1.2.2 Calling functions#

Now let’s try using our function. Calling our self-defined function is no different from calling any other function such as print(). You need to call it with its name and provide your value(s) as the required parameter(s) inside the parentheses. Here, we can define a variable freezing_point that is the temperature in degrees Fahrenheit we get when using our function with the temperature 0°C (the temperature at which water freezes). We can then print that value to confirm. We should get a temperature of 32°F.

def celsius_to_fahr(temp):
    return 9 / 5 * temp + 32
freezing_point = celsius_to_fahr(0)
print(f"The freezing point of water in Fahrenheit is {freezing_point}")
The freezing point of water in Fahrenheit is 32.0

We can do the same thing with the boiling point of water in degrees Celsius (100°C). Just like with other functions, we can use our new function directly within something like the print() function to print out the boiling point of water in degrees Fahrenheit.

print(f"The boiling point of water in Fahrenheit is {celsius_to_fahr(100)}")
The boiling point of water in Fahrenheit is 212.0

1.2.3 Let’s make another function#

Now that we know how to create a function to convert Celsius to Fahrenheit, let’s create another function called kelvins_to_celsius. We can define this just like we did with our celsius_to_fahr() function, noting that the Celsius temperature is just the temperature in Kelvins minus 273.15. Just to avoid confusion this time, let’s call the temperature variable used in the function temp_kelvins.

Note

It would be perfectly fine to use the variable name temp inside our new function as well. As you will see below, variables defined in functions exist only within the function itself and will not conflict with other variables defined in other functions.

def kelvins_to_celsius(temp_kelvins):
    temp_celsius = temp_kelvins - 273.15
    return temp_celsius
Using our second function#

Let’s use it in the same way as the earlier one by defining a new variable absolute_zero that is the Celsius temperature of 0 Kelvins. Note that we can also use the parameter name temp_kelvins when calling the function to explicitly state which variable value is being used. Again, let’s print the result to confirm everything works.

absolute_zero = kelvins_to_celsius(temp_kelvins=0)
print(f"Absolute zero in Celsius is {absolute_zero}")
Absolute zero in Celsius is -273.15

1.2.4 Check your understanding#

Create a new function called kelvins_fahr that

  • Has one parameter that is the temperature in degrees Kelvins to be converted to degrees Fahrenheit

  • Returns the temperature in degrees Fahrenheit

  • Recall this function to calculate temperature of absolute zero in degrees Fahrenheit

Note first convert from Kevins to Celsius temp_celsius=temp_kelvins - 273.15 and then from Celsius to Fahrenheit temp_fahrenheit= (temp_celsius*9/5) + 32

Ans. -459.66999999999996

Hide code cell content
# Write your function here
def kelvins_fahr(temp_kelvins):
    temp_celsius=temp_kelvins - 273.15
    temp_fahr= (temp_celsius*9/5) + 32
    return temp_fahr
# Call your funcyion from here and print results
print(kelvins_fahr(0))
-459.66999999999996

1.3 Functions within a function#

You can use functions that you created inside a function. In the previous example, instead of writing these formulas gain, we can simply call the two functions that we have previously created that are kelvins_to_celsius and celsius_to_fahr.

Try to create a new function kelvins_to_fahr that takes the temperature in Kelvins as the parameter value temp_kelvins and uses our kelvins_to_celsius and celsius_to_fahr functions within the new function to convert temperatures from Kelvins to degrees Fahrenheit.

def kelvins_to_fahr(temp_kelvins):
    temp_celsius = kelvins_to_celsius(temp_kelvins)
    temp_fahr = celsius_to_fahr(temp_celsius)
    return temp_fahr

Using our combined functions#

Now let’s use the function to calculate the temperature of absolute zero in degrees Fahrenheit. We can then print that value to the screen again.

absolute_zero_fahr = kelvins_to_fahr(temp_kelvins=0)
print(f"Absolute zero in Fahrenheit is {absolute_zero_fahr}")
Absolute zero in Fahrenheit is -459.66999999999996

1.4 Functions generally have multiple inputs and multiple outputs#

In this example, the function calculate_sum_and_product takes two input parameters (x1 and x2). Inside the function, it calculates the sum, product , and power of these two numbers and returns them as a tuple. When calling the function, you can use multiple variables to capture the multiple return values.

def sum_product(x1, x2):
    
    # Calculate the sum
    sum = x1 + x2
    
    # Calculate the product
    prod = x1 * x2

    # Calculate the power
    power = x1 ** x2
    
    # Return both results
    return sum, prod, power
# Example usage:
num1 = 5
num2 = 3

# Call the function and store the results in variables
sum_result, product_result,power_result = sum_product(num1,num2)


# Print the results
print(f"{num1} + {num2} = {sum_result}")
print(f"{num1} x {num2} = {product_result}")
print(f"{num1} ^ {num2} = {power_result}")
5 + 3 = 8
5 x 3 = 15
5 ^ 3 = 125

Note in the above example, the names of the input and output variables are not consistant, that is fine because they are defined by their order and not their variable names, unless you specify the variable name,e.g., sum_product(x1=5, num2). If you specified all variable names then the order is not not important, e.g., sum_product(x2=3, x1=5). You can also directly input the values, e.g., sum_product(5, 3). The same is true for the output variable names.

Check your understanding#

Write a function math_operation that takes numbers num1 and num2 and returns results and operation such that if the sum of the two numbers is less than 10, then the two numbers will be multiplied and in that case the operation will be “Multiplication”; if the sum of the two numbers are larger than or equal 10, then the two numbers will be added and in that case the operation will be “Addition”. Here is an example of your output

Multiplication of 8 and 2 is 16

Try to write the function and print results using the given function call

#Write your function here
def math_operation(num1, num2):
    sum = num1+num2
    if sum <10:
        math =num1*num2
        operation = "Multiplication"
    else:
        math = num1+num2
        operation = "Addition"
    return math, operation 

# Call you function using these two numbers
x=8
y=2
results=math_operation(x,y)

# Print output
print(f"{results[1]} of {x} and {y} is {results[0]}")
Addition of 8 and 2 is 10

2. Functions and variable names (advanced topic)#

2.1 A local variable in function is not accessible to global namespace#

A common point of confusion for new programmers is understanding how variable names in functions relate to those defined elsewhere in their code (or your notebooks). When defining a function, the variable names given in the function definition exist and will only be used when the function is called. That might seem confusing, but as it turns out this is an excellent feature in Python that can save you from much suffering. Let’s try to understand this by way of an example.

Let us define modified version of our kelvins_to_celsius function where the parameter value is still called temp_kelvins, but we now store the converted temperature as temp_celsius first and return that value.

def kelvins_to_celsius(temp_kelvins):
    temp_celsius = temp_kelvins - 273.15
    return temp_celsius

So, we have defined our function to accept temp_kelvins as its only parameter, calculate temp_celsius, and return that value. As you will see below, the variables defined in the function exist only in its namespace. Let’s confirm that by printing each out to the screen.

temp_kelvins
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 1
----> 1 temp_kelvins

NameError: name 'temp_kelvins' is not defined
temp_celsius
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[20], line 1
----> 1 temp_celsius

NameError: name 'temp_celsius' is not defined

Here, in the global namespace we get a NameError when trying to access the variables temp_kelvins and temp_celsius because they have only been defined within the scope of the kelvins_to_celsius() function.

Perhaps, however, you are thinking that we have not yet called the function, so that is why we get a NameError. Maybe if we use the function these variable values will be defined. Let’s try that and print out temp_kelvins once again.

kelvins_to_celsius(temp_kelvins=293.15)
20.0
temp_kelvins
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 temp_kelvins

NameError: name 'temp_kelvins' is not defined

As you can see temp_kelvins is still not defined in the global namespace, where values such as freezing_point have been defined.

2.2 A local variable in function does not affect a global variable of same name#

Changing a variable in a function will not change a variable with the same name in global namespace.

Why does Python or generally most programming languages work this way?

Well, as it turns out, the benefit of having a separate namespace for functions is that we can define a variable in the global namespace, such as temp_kelvins and not need to worry about its name within a function, or the use of a function changing its value. Inside the function, the value that is passed will be known as temp_kelvins, but modifying that value will not alter a variable of the same name in the global namespace. Let’s have a look at another example using a modified kelvins_to_celsius() function we can call kelvins_to_celsius2().

def kelvins_to_celsius2(temperature):
    temperature = temperature - 273.15
    return temperature

Here, we pass in a value as temperature and modify the value that is passed in before returning it. This is probably not a good idea in most cases because it could cause confusion, but it is perfectly valid code.

Let’s now define a variable temperature in the global namespace, use our function with it, and print its value out to the screen afterwards.

temperature = 303.15
kelvins_to_celsius2(temperature=temperature)
30.0
temperature
303.15

As you can see, the value of the variable temperature in the global namespace was set to 303.15 and remains 303.15 even after using the kelvins_to_celsius2() function. Although there is a variable inside that function with the same name as the value in the global namespace, using the function assigns the value of temperature inside the function and manipulates that value only inside the function.

2.3 A global variables is accessible to a function without explicit passing#

Any gobal variable is accessiable and can be used by the function even if it was not explicitly passed to the function.

Caution

It is important to be aware that it is possible to access variable values that have been defined in the global namespace from within functions, even if the value has not been passed to the function. This is because Python will search for variables defined with a given name first inside the function, and then outside the function (the search domain is known as the variable’s scope). If such a value is found then it can be used by the function, which could be dangerous!

Let’s look at an example of behavior in a function that may be unexpected. Here we can define a third version of the kelvins_to_celsius() function

def kelvins_to_celsius3(temp_kelvins):
    temp = temperature - 273.15
    return temp
kelvins_to_celsius3(273.15)
30.0

Perhaps you were expecting to see a value of 0 returned by kelvins_to_celsius3(), but that does not occur because temp is assigned temperature - 273.15 inside the function. Although temperature was not passed to kelvins_to_celsius3() it is defined in the global namespace and thus can be used by our example function. Since temperature = 303.15 we get a value of 30.0 returned when using kelvins_to_celsius3(). Conclusion: Be careful!

If you understand that much, you have a very good understanding of functions in programming langugage and specifically in python. For those who are interested, more information about namespaces and variables scopes can be found on the Real Python website.