7. Functions, Modules and Libraries#

So far, we have mostly focused on small pieces of code that perform specific tasks. In larger programs, the same types of tasks are often performed several times in different sections of the code. In such situations, it can be efficient to reuse particular pieces of code. Imagine, for example, that you want to write a program in Python which can calculate the final grades earned by students who have followed a certain course. Such grades are normally calculated using a fixed formula (e.g. a mid-term assignment which counts for 40% and a final exam which counts for 60%). To calculate the final grades, this formula needs to be applied repeatedly with different values. In such a situation, it would be quite inefficient if you simply repeated the code that is needed for every single student. Fortunately, we can reuse fragments of code by defining them as functions.

Functions#

A function is essentially a set of statements which can be addressed collectively via a single name. A function may demand some input. The values that need to be provided as input are referred to as parameters. Functions can require zero or more parameters. The function may also produce some results as output.

We have already seen a number of examples of functions that Python provides ‘out of the box’. The list below gives a number of examples of such built-in functions:

  • print() takes one or more values and ‘writes’ these to the screen.

  • len() takes a string, list or dictionary and returns its length (i.e. the number of characters or items).

  • float() takes a numerical value (e.g. an integer) as input, and converts it into an integer.

  • dict() takes no inputs and returns an empty dictionary.

  • sorted() takes a list and returns a sorted copy of that list.

You can find more information about all built-in functions in the Python documentation.

When you use a function, you often say that you are calling a function. When you call a function, you always include the parentheses after the name of the function, even when there are no parameters.

# Returns an empty dictionary
dict()

The in-built function round(), which rounds numbers, takes two parameters. The first parameter is a floating point number that needs to be rounded. The second parameter is the number of digits you would like to see following the decimal point.

# Keep 2 decimals
round(4.55892,2)

If you come across a function that you do not understand, you can always ask for more information using the built-in help() function. In this help() function, you need to provide the name of the function as an argument, without the parentheses.

# Ask for documentation of the print function
help(print)

Defining functions#

Next to working with in-built functions, it is also possible to write your own functions. Working with functions can often be very effective. It enables you to decompose specific problem into smaller sub-problems and into units which can be reused as often as needed. Follow the steps below to define a new function.

  • You begin the definition of the function with def.

  • After def, you firstly provide the name of the function. Function names need to follow the same rules as variable names.

  • The name of the function is followed by a set of parentheses. Within these parentheses, you mention the parameters: the values that the function works with. If there are two or more parameters, they need to be separated by commas. You always need to type in the parentheses, even if the function does not demand any parameters. In this latter case, you simply provide an empty set of parentheses.

  • Give a colon after the closing bracket.

  • Following the line that starts with def, you provide an indented block of code containing all the actual statements that make up the function.

  • If the function also produces output, this final result needs to be given after the keyword return.

When you choose a name, remember that you cannot use a reserved keyword, and that it is advisable to work with a descriptive name. The Python community has [guidelines for naming functions][pep8-func]. It is recommended to come up with function names that are “lowercase, with words separated by underscores to improve readability”. [pep8-func]: https://peps.python.org/pep-0008/#function-and-variable-names

The cell below contains an example of a newly defined function. The function that is programmed calculates the area of a rectangle, by multiplying the width and the height.

def calculate_area(width,height):
    area = width * height
    return area

When you define a function, this does not immediately run the code. The cell above does not produce any output. Defining a function is similar to defining a variable. To run the code, you need to call the function, using actual values for the parameters that are required.

print( calculate_area(5,6) )

Once the function has been defined, it can be called, or invoked, in other locations in your program. When you call the function, the parameters named width and height will both be assigned values. More specifically, they will be assigned the values provided as parameters. The calculation inside the body of the function then operates on these specific values. The print() statement in the cell above eventually outputs the result of the calculation.

Remember that a function always returns something. If a function does not include a return statement, the function automatically returns the value None.

Exercise 7.1.#

Write a function which can convert a given temperature in degrees Celcius into the equivalent in Fahrenheit. Use the following formula: F = 1.8 * C + 32.

Once the function is ready, test it with a number of values. 20 degrees Celcius ought to be converted into 68 degrees Fahrenheit, and 37 degrees Celcius should equal 98.6 degrees Fahrenheit.

print(celcius_to_fahrenheit(20))
print(celcius_to_fahrenheit(37))

Exercise 7.2.#

Write a function which can test whether a given number is in between 10 and 20. The function should return True if this is the case, and False if not. The function should also return False is equal to 10 or to 20.

print(between_10_and_20(8))
print(between_10_and_20(14))
print(between_10_and_20(29))
print(between_10_and_20(20))

Exercise 7.3.#

In a given university course, the final grade is determined by grade for essay and by the grade for a presentation. The presentation counts for 30% and the essay for 70%.

Write two functions:

  1. calculate_mark should calculate the final grade based on a set of partial grades according to the given formula. Grades must be rounded to integers. 5.4, for example, becomes 5 and 6.6 becomes 7.

  2. pass_or_fail should determine whether a given grade is at a pass level (i.e. equal to or higher than 6). This function must return a string value, ‘Pass’ or ‘Fail’.

# Function calculate_mark determines the final mark, based on the marks for the essay and presentation


# Function pass_or_fail determines 'Pass' or 'Fail', based on a single mark
# Let's try them:
essay1 = 7.0
presentation1 = 8.5
final1 = calculate_mark(essay1, presentation1)
# We expect the grade 7 (pass)
print( f"final grade: {final1} ({pass_or_fail(final1)})" )

essay2 = 4.5
presentation2 = 5.5
final2 = calculate_mark(essay2, presentation2)
# We expect the grade 5 (fail)
print( f"final grade: {final2} ({pass_or_fail(final2)})" )

Optional parameters#

Parameters can be made optional by specifying default values in the function definition. The function named shorten_string() below contains an illustration. It can be used to shorten a string. Such a function can be useful if you want to display shortened titles in a library catalogue, for example. By default, the function selects the first twelve characters only. If a different length is needed, the required number of characters can be specified as the second parameter.

def shorten_string( text , max_characters = 12 ):
    return text[:max_characters] + ' [...]'

print(shorten_string('Frankenstein, or, The Modern Prometheus'))
print(shorten_string('The Secret Agent: A Simple Tale' , 16))

The in-built function round(), which was discussed above, similarly works with optional parameters. The first parameter is the floating point number that needs to be rounded off. The second parameter is the number of digits you would like to see following the decimal point. The default value for this second parameter is 0 (zero). If you supply only one parameter, the number is rounded off to a number with zero decimals, or, in other words, to an integer.

float_number = 4.55892

# Keep 2 decimals
print( round(float_number, 2) )

# Round to zero decimals and return an integer
print( round(float_number) )

Exercise 7.4.#

Write a function which takes a Python list as a parameter. This list should contain numbers. The function should return the average value of the numbers in the list. Name the function calculate_average(). in the definition of this function, add an optional parameter which specifies the number of digits that are returned after the digit. The default value must be 1.

To calculate the sum of all the numbers in a list, you can work with the sum() function.

numbers = [6,67,8,33,24,11,34,6,23,4,19,6,9,12,134,45,23]

Classes and methods#

In Python, it is also possible to make functions for specific types of variables. Such functions which can only be applied to specific types of variables are referred to as methods.

  • In the case of strings, for example, there is a method named upper(), which we can use to conver all the characters in the string to upper case.

  • The method get() can be used with dictionaries, to obtain the value associated with a given key.

  • The method count() is availabe for lists. This method can be used to count the number of occurrences of a given item.

Strings, lists and dictionaries are all examples of variable types. As a programmer, you can also create new variable types. When you want to do this, thes variable types should be defined as a classes.

A class, simply put, is a container which brings together certain variables and certain functions. Within the context of the class, these are referred to as properties and methods, respectively. After defining a class, you can use it as a data type.

When you create a calendar application, for example, it would be easier to work with an Event class that has properties for start time, end time and description, than to use a dictionary with these elements. This Event class can have methods for determining whether it is a past event or future event, or whether its end time is not before its start time. The code below gives a demonstration of how such an Event class can be implemented.

class Event:
    def __init__(self, start_time, end_time, description):
        self.start_time = start_time
        self.end_time = end_time
        self.description = description

    def describe(self):
        print("Event: "+str(self.description))
        print("Starts: "+self.start_time)
        print("Ends: "+self.end_time )

This paradigm of creating classes with properties and methods is known as object-oriented programming. A full explanation of the details of the concept of object-oriented programming is beyond the scope of this tutorial. The explanation that follows is only for those who are curious, but it may equally be skipped.

The class that was defined, Event, has two methods: __init__ and describe. The first parameter of a method within a class is always self and refers to the instance of the class that the method is being called on. Through the self reference, you can refer to an object’s properties (self.end_time, for example) and methods.

You can use a class by instantiating it. This means that you create an instance of the class. This instance of the class is called an object, and it can be assigned to a variable. You can access the properties and methods of the object by appending the names of these properties and methods to the object, following a period. This notation is called the period syntax. The __init__ method is exceptional, as this method is invoked automatically when you create a new Event object.

The code below indicates how the class Event can be initiated.

new_event = Event("2024-10-15", "2024-11-15",'Programming course')

Once the new_event object is defined, you can invoke its describe() method, using the period syntax.

new_event.describe()

It is no problem if you did not fully understand the exlanation of object-oriented programming that was given in this section, but it hopefully helps to clarify the nature of methods.

Like the Event class, which was defined above, the in-built String class can be instantiated as an object. Such instances of String objects have access to all the methods which are defined for strings, including lower(), strip() and index().

Modules#

When you have a number of functions or classes that perform similar or related activities, these can all be stored together in a Python file. Such a file containing related functions, statements or classes is called a module. Modules can be imported into programs, so that their methods and classes can be reused.

When you download and install Python, the installation typically includes many frequently used modules. The collection of in-built modules is called the Python Standard Library. This standard library includes modules such as math (which contain mathematical functions) and random (containing functions for generating random numbers).

Packages#

One or more modules can be brought together in a package. At a concrete level, a package is a folder containing modules. Such a package may be seen as a wrapper for modules and other scripts which perform related functions.

Packages can also be published online. One of the most important repositories for Python code is the Python Package Index (PyPI). Via PyPI, Python developers can make their code available for reuse. If developers can build on code that has already been created and tested by others, this can obviously save them much time. Packages are sometimes referred to as ‘libraries’ as well.

There are thousands of projects that you can install from the web. The following packages are used frequently in data science:

Packages which have been published on PyPI can usually be installed on your local machine using pip, the package installer for Python.

pip install nltk

The modules and packages in the Python Standard Library obviously do not need to be installed separately.

Importing modules#

If a package has been installed successfully on your computer, it can be imported into your code using import. The Python Standard Library has a module named os, which contains various functions for working with files and folders on your operating system.

import os

os stands for ‘Operating System’. This module will be discussed in more detail in the section focusing on working with files and directories.

Once imported, all the functions and the classes that are defined within this package can be used via the period syntax. The function listdir(), for instance, can be invoked using the code below:

files = os.listdir('Solutions')

Alternatively, it is also possible to import individual functions from a module.

from os import listdir

This second way of importing code has the advantage that it is no longer necessary to use the period syntax. The function can then be used without referencing the name of the module:

files = listdir('Solutions')

Exercise 7.5.#

Import the math module, as follows:

from math import *

This command imports all the available functions from the math module. Use the functions log10(), pow(), sqrt() and cos() to generate the following numbers:

  • The base-10 logarithm of 5.

  • 3 raised to the power of 4

  • The square root of 144

  • The cosine of 60 radians.

# Import the library

from math import log10
# Print the four numbers
log10(6)

Exercise 7.6.#

Calculate the length of the hypothenuse in a right trangle in which the other two sides have a length of 6 and 7, following Pythagoras’ theorem (A2 + B2 = C2). Make use of the math module.

Exercise 7.7.#

The random module can be used to make random numbers, or to select items from a list randomly.

  • Using the randrange() method, write code to create a dice rolling application. In other words, write code which can generate a random number in between 1 and 6.

  • Secondly, work with the choice() method from random to select a random number from the list named game.

game = ['rock','paper','scissors']

Exercise 7.8.#

Instead of saving dates simply as strings, you can also save them as datetime objects. This can be done using the datetime module. Saving dates as datetime objects can be very helpful when you want to perform calculations on dates. For this exercise, calculate the number of days that have passed since January 1 2022. Follow the steps below.

  1. datetime is a module. Within this module, there is also a class named date. Start by importing the datetime class from the datetime module.

  2. Define ‘January 1 2022’ as a datetime. You can do this using the constructor method of datetime. This method demands three parameters: the year, the month and the date. These three numbers should all be integers. Assign the date to a variable named older_date

  3. Find the current date. The datetime class in datetime contains a method called today() that you can use for this purpose. Assign today’s date to a variable named now.

  4. Subtract older_date from now. Append the property days to the result to see only the number of days.