Functions

Tasks

  1. Complete reading this lesson, which involves the following:
    1. Introduction to the Functions
    2. Void Functions
    3. Scope
    4. Passing Arguments
    5. Returning Values
    6. Docstring
    7. Developing Modules
  2. Read and complete the activities at ZyBook, Z4: Functions.
  3. Complete quiz 3 to verify your reading and understanding.

Learning Outcomes

By the end of this lesson, you should be able to:

  1. Use functions in Python programs
  2. Explain the design of modular programs
  3. Describe variable scope
  4. Use local and global variables in programs
  5. Develop functions with parameters
  6. Return values from a function
  7. Write docstring to document functions
  8. Develop and use modules in programs

Key Terms/Concepts

Introduction

Functions are a powerful programming concept that makes a program design modular. In a nutshell, a function is a group of statements. A programmer can call and execute that group of statements through the function name. That means once a useful function has been created (like the built-in functions input, type, print) it can be called and executed multiple times from within a program. If the function was designed well and it is generic enough, it can be used across programs too.

Good programming practices suggest that a single function should perform one dedicated task. The function should be written using variables, rather than hard coded values so that it is generic in nature and can be reused effectively and easily.

We already know some built-in functions. Take inputas an example. Users can provide a custom string to this function, which is displayed on the console and the input returns a user entered value as a string.

Similarly print can be used to output a varying number of integers, floats, and strings. The order and combination can vary. Let's think of the functions that we can make in our programs. Suppose we are making a program to calculate employee salary. We will be expected to design a program that does the following:

It seems that we might need two functions in this program (see Figure 1 ). The first function calculates the gross pay. This function use the number of hours worked and the rate of pay to calculate the salary. It then adds benefits like transport allowance to the salary and return back the data.

The second function takes the gross pay produced by the first function and makes necessary deductions. It can deduct provincial tax, federal tax, and union fees and return back the take home salary.

Modular programs are similar to long reports. Rather than having the entire report under a single heading, multiple headings are created and the content is divided in sections.

Two Functions Called from Main Program
Program takes the input from the user, and then calls a function to calculate gross pay. Gross pay is returned back to the program. The second function is then called from the program to calculate deductions. Necessary information is sent back from the second function to be printed on the console.
Created by Muhammad Muddassir Malik. Used with permission.

Benefits of using Functions

Functions have a lot of benefits and for that reason they are extensively used in programming to make source code modular. Some benefits are listed below:

Reuse

There is no need to duplicate the code and write the same sequence of statements multiple times. Once a function has been designed, it can easily be called as many times as needed in a program. Functions can be reused in other programs as well like you used print and type() in previous modules. For example, the function for the calculation of gross pay (Figure 1) can be reused in another program being designed for managing the payroll system of another company.

Divide and Conquer Strategy

You do not need to try to solve the entire problem once. You can divide the program into sub tasks and solve each one separately.

Enhanced Code Readability

Teams work together to develop applications. The need to read and use or modify a source code comes up quite often. Functions make code reading and understanding much easier. Code readability is also the reason why comments are encouraged in code. In this lesson we also discuss docstrings, which are used to document functions.

Simpler Code

There is no need to solve the same problem multiple times. Also, different small tasks are solved in separate functions. Therefore, the code is simpler and is easier to modify or to enhance to add more functionality.

Team Work

Modular design assists team work. Different team members can work on separate functions and later on the functions developed by various programmers can be combined together in a program.

Quality Assurance

Quality Assurance is a critical part of software development. Modularized programs help quality assurance engineers to do a better job. They test each function separately (unit testing) and make it part of the program. Any subsequent changes to a function requires retesting of a specific function. This helps pinpoint the origin of logical errors in the program.

In this lesson we start by writing simple functions. Functions also effect the visibility and behaviour of variables, which comes under the topic of scope. Afterwards we study how data can be passed to the function and how functions can send back data. We then discuss docstrings, which are used to document functions and also create custom modules.

Functions

Defining a Function

We start by converting an existing program without functions into a program that uses a function. In Lesson 3 we wrote a simple program to convert a Fahrentheit temperature to celsius:

Fahrenheit to Celsius Program

# Constants
FAHRENHEIT_FREEZING = 32
C_F_RATIO = 5 / 9    # celsius to fahrenheit ratio

fahrenheit = float(input("Enter temperature (F): "))
celsius = (fahrenheit - FAHRENHEIT_FREEZING) * C_F_RATIO
print(f"Temperature (C): {celsius:.2f}")

The important part of this program is the conversion from Fahrenheit to Celsius, and that is the part we are going to convert to a function. The input and output is not going to be part of the function as that is a separate concern - it is not the function's job to either get input from the user or to output the result.

A function must be defined in a PyDev module in the same way that the program above was defined in a module (t02.py, for example). For ease of reuse functions are generally defined in their own module, or library. (Function libraries were discussed in Lesson 3.) These libraries should not be in the same modules that use the functions, as is discussed later.

A function needs to have a distinct name. All the variable naming rules (discussed in Lesson 2) also apply to functions. We start by writing a function named f_to_c:

A Simple Function

# Constants
FAHRENHEIT_FREEZING = 32
C_F_RATIO = 5 / 9    # Celsius to fahrenheit ratio

def f_to_c(fahrenheit):
    """
    -------------------------------------------------------
    Converts temperatures in Fahrenheit to Celsius.
    Use: celsius = f_to_c(fahrenheit)
    -------------------------------------------------------
    Parameters:
        fahrenheit - temperature in fahrenheit (int >= -459)
    Returns:
        celsius - equivalent temperatur in celsius (float)
    -------------------------------------------------------
    """
    celsius = (fahrenheit - FAHRENHEIT_FREEZING) * C_F_RATIO
    return celsius

The function has both similarities with and differences from the program referred to in Code List 1. The similarities are:

The differences are:


Calling a Function

Calling a function simply means that your code is going to invoke, or ask the function to execute, by giving the function name, provide it with appropriate parameters (if any), and capture its return value(s) (if any). The following shows how to call the f_to_c function:

Calling A Simple Function

# Imports
from functions import f_to_c

# Use a literal value
celsius1 = f_to_c(37)
print(f"Temperature (C): {celsius}")

# Ask the user for a value
fahrenheit = int(input("Temperature (F): "))
celsius2 = f_to_c(fahrenheit)
print(f"Converted Temperature: {celsius} (C)")

This code breaks down as:

The point of this example is to illustrate that functions can be called in many ways. The only required consistency is that the number and type of parameters must match the function's required parameters. The names are irrelevant.

We can define multiple functions in a Python module. One function can call other functions. The ordering of their definitions does matter.

Scope

We are using variables in our programs. Values are assigned to the variables and then we can use the value using variable name. With the introduction of modular programming, an important question arises: Is every variable accessible in each function of the program or not?

Scope dictates the rules about the visibility of a variable in any function.

The following trivial example shows how the scope of the variable x is determined by where it is defined:

The Scope of x

x = "Global variable"

def func1():
    x = "inside func1"
    print(x)
    return

def func2():
    x = "inside func2"
    print(x)
    return

print(x)
func1()
print(x)
func2()
print(x)

This produces the output:

The Scope of x
Global variable
inside func1
Global variable
inside func2
Global variable

This sample output clearly illustrates that even though functions and the main code do not share the variables that they each declare.

This is a very artificial example used only to illustrate scope. We normally define functions in modules separate from the modules that call the functions, and we do not use global variables in this course. Thus situations like this do not often arise in our programming style.

We do use global constants, in the sense of defining constants that can be used by many functions in the same module, or accessed by a calling module like pi from the math library, or FAHRENHEIT_FREEZING from a previous example.

Arguments and Parameters

We have been showing examples of functions with parameters, but want to discuss them more formally in this section. Arguments and parameters are a way to pass data to a function. A function does not care where the values it processes come from so long as they are in the correct order and of the correct type. The values defined within the function are parameters, and the values that are passed to a function when it is called are arguments. There is a one-to-one correspondence between arguments and parameters. We will illustrate this with a simple function and call to that function.

This function takes two parameters:

Salary Calculation

def calculate_salary(hours_worked, pay_rate):
    """
    -------------------------------------------------------
    Calculates a total salary.
    Use: total_salary = calculate_salary(hours_worked, pay_rate)
    -------------------------------------------------------
    Parameters:
        hours_worked - hours worked by employee (float >= 0.0)
        pay_rate - hourly pay rate for employee (float > 0.0)
    Returns:
        total_salary - employee's total salary (float)
    -------------------------------------------------------
    """
    total_salary = hours_worked * pay_rate
    return total_salary

and the code that calls this function:

Calling calculate_salary

from functions import calculate_salary

hours = float(input("Enter the total hours worked: "))
rate = float(input("Enter hourly rate of pay: "))
total = calculate_salary(hours, rate)
print(f"Total salary: ${total:,.2f}")

This function and the call to it illustrate the use of parameters and arguments:

The output for a sample run of the given program is:

Sample Execution
Enter the total hours worked: 10.0
Enter hourly rate of pay: 16.55
Total salary: $165.50

Note that it was not the job of the function to print the results - that was up to the program that called the function and captured its return value. Return values are discussed in detail in the next section.

Returning Values

A return statement is used to send data from a function to whatever calls that function. For example, the built-in input function returns the value entered by the user. This value is assigned to the variable before the assignment operator. See the first line of code below:


student_name = input(“Enter name: ”)
cost = float(input("Enter cost $: "))

We have used the input and float functions before, but what is actually happening here?

We have already seen the f_to_c function, and how it accepts a Fahrenheit temperature parameter and returns a single celsius value, and how the calling code provides an argument to the function and captures that single returned value in a variable.

Python functions may return zero, one, or many values in their return statements. The following function takes five parameters and returns three values:

Returning Multiple Values

def stats(no1, no2, no3, no4, no5):
    """
    -------------------------------------------------------
    Calculates simple statistics on five numbers.
    Use: total, average, max_val = stats(no1, no2, no3, no4, no5)
    -------------------------------------------------------
    Parameters:
        no1, no2, no3, no4, no5 - numbers (float)
    Returns:
        total - sum of parameters (float)
        average - average of parameters (float)
        max_val - maximum of parameters (float)
    -------------------------------------------------------
    """
    total = no1 + no2 + no3 + no4 + no5
    average = total / 5
    max_val = max(no1, no2, no3, no4, no5)
    return total, average, max_val

The function calculates and returns three values. The return statement names those values in a comma-separated list.

Sample code to call the function:


from functions import stats

no1 = 12
no2 = 14
no3 = -8
no4 = 2
no5 = -3

tot, avg, mval = stats(no1, no2, no3, no4, no5)

print(f"Sum:     {tot:8.2f}")
print(f"Average: {avg:8.2f}")
print(f"Maximum: {mval:8.2f}")

This code calls the function and passes it the required parameters.

And the results of executing the sample call:

Sum:        17.00
Average:     3.40
Maximum:    14.00

Docstrings

We discussed the importance of comments in programming. A docstring is a Python method of commenting that documents functions and modules. There are a few variants of docstring styling, but the objective is the same for all of them. Docstrings provides the following information about a given function:

The following is an example of docstring for the stat function:

Sample Docstring

def stats(no1, no2, no3, no4, no5):
    """
    -------------------------------------------------------
    Calculates simple statistics on five numbers.
    Use: total, average, max_val = stats(no1, no2, no3, no4, no5)
    -------------------------------------------------------
    Parameters:
        no1, no2, no3, no4, no5 - numbers (float)
    Returns:
        total - sum of parameters (float)
        average - average of parameters (float)
        max_val - maximum of parameters (float)
    -------------------------------------------------------
    """

This docstring tells us that the function:

Developing Modules

Modules or libraries are collections of functions that can be used in multiple programs. Earlier you have worked with the built-in math module. This module contains a wide range of basic mathematical functions and constants.

Similarly, when making larger programs you should organize your own code into modules. If creating a payroll application, all the functions that deal with taxes and deductions can be in one module, whereas all the functions that produce reports and data for the management can be in another module.

A module is simple to create. It is just a collection of functions and/or constants. The following is a sample module, energy.py:

The Energy (energy.py) Module

"""
-------------------------------------------------------
Collection of energy functions and constants.
-------------------------------------------------------
Author:  David Brown
ID:      123456789
Email:   dbrown@wlu.ca
__updated__ = "2020-10-06"
-------------------------------------------------------
"""
# Imports
from math import pow

# Constants
# acceleration due to gravity near the surface of the earth (m/s^2)
GRAVITY_ACCEL = 9.8   

def compute_kinetic_energy(mass, velocity):
    """
    -------------------------------------------------------
    Calculates kinetic energy of an object.
    Use: kinetic_energy = compute_kinetic_energy(mass, velocity)
    -------------------------------------------------------
    Parameters:
        mass - mass of the object (int > 0)
        velocity - velocity of the object (int > 0)
    Returns
        kinetic_energy - kinetic energy of the object (float)
    -------------------------------------------------------
    """
    kinetic_energy = mass * pow(velocity, 2) / 2
    return kinetic_energy

def compute_potential_energy(mass, height):
    """
    -------------------------------------------------------
    Calculates potential energy of an object
    Use: potential_energy = compute_potential_energy(mass, height)
    -------------------------------------------------------
    Parameters:
        mass - mass of the object (float > 0)
        height - third quiz score (int > 0)
    Returns
        potential_energy - potential energy of the object (float)
    -------------------------------------------------------
    """
    potential_energy = mass * GRAVITY_ACCEL * height
    return potential_energy

This module contains two functions and one constant, and uses another module (the math module) to perform its calculations. The entire module begins with a docstring and each function contains it own docstring. Note that the constant is defined at the beginning of the module and can therefore be used by all functions within the module as necessary, and be available for import into another module.

Importing and using this module is done the same way as importing and using the math module, as in this example:

Using the Energy Module

from energy import GRAVITY_ACCEL, compute_kinetic_energy, compute_potential_energy

print(f"The following assume a gravitational acceleration of {GRAVITY_ACCEL:.2f} m/s^2")
print()

mass = float(input("Mass (N): "))
velocity = float(input("Velocity (m/s):"))

ke = compute_kinetic_energy(mass, velocity)
print(f"The kinetic energy is: {ke:.2f}")

mass = float(input("Mass (N): "))
height = float(input("Height (m): "))

pe = compute_potential_energy(mass, height)
print(f"The potential energy is: {pe:.2f}")

This program imports and executes two of the functions in the energy module, and also imports and prints the acceleration due to gravity constant. This constant could be used in further calculations if necessary.

Modularity is key to organized and efficient program design.

Conclusion

In this lesson we learned about modular programming. It is a powerful concept to make large programs manageable and well organized. We started by creating void functions. Later on, we discussed scoping, how we can pass data to the function using arguments, and how we can get data back from the function using the return keyword. In the last two sections, we covered function documentation and the use of modules.