24. Functions
By Bernd Klein. Last modified: 08 Nov 2023.
syntax
The concept of a function is one of the most important in mathematics. A common usage of functions in computer languages is to implement mathematical functions. Such a function is computing one or more results, which are entirely determined by the parameters passed to it.
This is mathematics, but we are talking about programming and Python. So what is a function in programming? In the most general sense, a function is a structuring element in programming languages to group a bunch of statements so they can be utilized in a program more than once. The only way to accomplish this without functions would be to reuse code by copying it and adapting it to different contexts, which would be a bad idea. Redundant code - repeating code in this case - should be avoided! Using functions usually enhances the comprehensibility and quality of a program. It also lowers the cost for development and maintenance of the software.
Functions are known under various names in programming languages, eg as subroutines, routines, procedures, methods, or subprograms.
Live Python training
See our Python training courses
Motivating Example of Functions
Let's look at the following code:
print ( "Program starts" )
print ( "Hi Peter" )
print ( "Nice to see you again!" )
print ( "Enjoy our video!" )
# some lines of codes
the_answer = 42
print ( "Hi Sarah" )
print ( "Nice to see you again!" )
print ( "Enjoy our video!" )
width , length = 3 , 4
area = width * length
print("Hi Dominque")
print("Nice to see you again!")
print("Enjoy our video!")
OUTPUT:
Program starts Hi Peter Nice to see you again! Enjoy our video! Hi Sarah Nice to see you again! Enjoy our video! Hi Dominque Nice to see you again! Enjoy our video!
Let us have a closer look at the code above:

You can see in the code that we are greeting three persons. Every time we use three print calls which are nearly the same. Just the name is different. This is what we call redundant code. We are repeating the code the times. This shouldn't be the case. This is the point where functions can and should be used in Python.
We could use wildcards in the code instead of names. In the following diagram we use a piece of the puzzle. We only write the code once and replace the puzzle piece with the corresponding name:

Of course, this was no correct Python code. We show how to do this in Python in the following section.
Functions in Python
The following code uses a function with the name greet. The previous puzzle piece is now a parameter with the name "name":
def greet(name):
print("Hi " + name)
print("Nice to see you again!")
print("Enjoy our video!")
print("Program starts")
greet("Peter")
# some lines of codes
the_answer = 42
greet("Sarah")
width, length = 3, 4
area = width * length
greet("Dominque")
OUTPUT:
Program starts Hi Peter Nice to see you again! Enjoy our video! Hi Sarah Nice to see you again! Enjoy our video! Hi Dominque Nice to see you again! Enjoy our video!

We used a function in the previous code. We saw that a function definition starts with the def keyword. The general syntax looks like this:
def function-name(Parameter list):
statements, i.e. the function body
The parameter list consists of none or more parameters. Parameters are called arguments, if the function is called. The function body consists of indented statements. The function body gets executed every time the function is called. We demonstrate this in the following picture:

The code from the picture can be seen in the following:
def f(x, y):
z = 2 * (x + y)
return z
print("Program starts!")
a = 3
res1 = f(a, 2+a)
print("Result of function call:", res1)
a = 4
b = 7
res2 = f(a, b)
print("Result of function call:", res2)
OUTPUT:
Program starts! Result of function call: 16 Result of function call: 22
We call the function twice in the program. The function has two parameters, which are called x and y. This means that the function f is expecting two values, or I should say "two objects". Firstly, we call this function with f(a, 2+a). This means that a goes to x and the result of 2+a (5) 'goes to' the variable y. The mechanism for assigning arguments to parameters is called argument passing. When we reach the return statement, the object referenced by z will be return, which means that it will be assigned to the variable res1. After leaving the function f, the variable z and the parameters x and y will be deleted automatically.

The references to the objects can be seen in the next diagram:

The next Python code block contains an example of a function without a return statement. We use the pass statement inside of this function. pass is a null operation. This means that when it is executed, nothing happens. It is useful as a placeholder in situations when a statement is required syntactically, but no code needs to be executed:
def doNothing():
pass
A more useful function:
def fahrenheit(T_in_celsius):
""" returns the temperature in degrees Fahrenheit """
return (T_in_celsius * 9 / 5) + 32
for t in (22.6, 25.8, 27.3, 29.8):
print(t, ": ", fahrenheit(t))
OUTPUT:
22.6 : 72.68 25.8 : 78.44 27.3 : 81.14 29.8 : 85.64
""" returns the temperature in degrees Fahrenheit """ is the so-called docstring. It is used by the help function:
help(fahrenheit)
OUTPUT:
Help on function fahrenheit in module __main__:
fahrenheit(T_in_celsius)
returns the temperature in degrees Fahrenheit

Our next example could be interesting for the calorie-conscious Python learners. We also had an exercise in the chapter Conditional Statements of our Python tutorial. We will create now a function version of the program.
The body mass index (BMI) is a value derived from the mass (w) and height (l) of a person. The BMI is defined as the body mass divided by the square of the body height, and is universally expressed
$BMI = {w \over {l ^ 2}}$
The weight is given in kg and the length in metres.
def BMI(weight, height):
""" calculates the BMI where
weight is in kg and height in metres"""
return weight / height**2
We like to write a function to evaluate the bmi values according to the following table:

The following shows code which is directly calculating the bmi and the evaluation of the bmi, but it is not using functions:
height = float(input("What is your height? "))
weight = float(input("What is your weight? "))
bmi = weight / height ** 2
print(bmi)
if bmi < 15:
print("Very severely underweight")
elif bmi < 16:
print("Severely underweight")
elif bmi < 18.5:
print("Underweight")
elif bmi < 25:
print("Normal (healthy weight)")
elif bmi < 30:
print("Overweight")
elif bmi < 35:
print("Obese Class I (Moderately obese)")
elif bmi < 40:
print("Obese Class II (Severely obese)")
else:
print("Obese Class III (Very severely obese)")
OUTPUT:
23.69576446280992 Normal (healthy weight)
Turn the previous code into proper functions and function calls.
def BMI(weight, height):
""" calculates the BMI where
weight is in kg and height in metres"""
return weight / height**2
def bmi_evaluate(bmi_value):
if bmi_value < 15:
result = "Very severely underweight"
elif bmi_value < 16:
result = "Severely underweight"
elif bmi_value < 18.5:
result = "Underweight"
elif bmi_value < 25:
result = "Normal (healthy weight)"
elif bmi_value < 30:
result = "Overweight"
elif bmi_value < 35:
result = "Obese Class I (Moderately obese)"
elif bmi_value < 40:
result = "Obese Class II (Severely obese)"
else:
result = "Obese Class III (Very severely obese)"
return result
Let us check these functions:
height = float(input("What is your height? "))
weight = float(input("What is your weight? "))
res = BMI(weight, height)
print(bmi_evaluate(res))
OUTPUT:
Normal (healthy weight)
Default arguments in Python
When we define a Python function, we can set a default value to a parameter. If the function is called without the argument, this default value will be assigned to the parameter. This makes a parameter optional. To say it in other words: Default parameters are parameters, which don't have to be given, if the function is called. In this case, the default values are used.
We will demonstrate the operating principle of default parameters with a simple example. The following function hello, - which isn't very useful, - greets a person. If no name is given, it will greet everybody:
def hello(name="everybody"):
""" Greets a person """
result = "Hello " + name + "!")
hello("Peter")
hello()
OUTPUT:
Hello Peter! Hello everybody!
The Defaults Pitfall
In the previous section we learned about default parameters. Default parameters are quite simple, but quite often programmers new to Python encounter a horrible and completely unexpected surprise. This surprise arises from the way Python treats the default arguments and the effects steming from mutable objects.
Mutable objects are those which can be changed after creation. In Python, dictionaries are examples of mutable objects. Passing mutable lists or dictionaries as default arguments to a function can have unforeseen effects. Programmer who use lists or dictionaries as default arguments to a function, expect the program to create a new list or dictionary every time that the function is called. However, this is not what actually happens. Default values will not be created when a function is called. Default values are created exactly once, when the function is defined, i.e. at compile-time.
Let us look at the following Python function "spammer" which is capable of creating a "bag" full of spam:
def spammer(bag=[]):
bag.append("spam")
return bag
Calling this function once without an argument, returns the expected result:
spammer()
OUTPUT:
['spam']
The surprise shows when we call the function again without an argument:
spammer()
OUTPUT:
['spam', 'spam']
Most programmers will have expected the same result as in the first call, i.e. ['spam']
To understand what is going on, you have to know what happens when the function is defined. The compiler creates an attribute __defaults__:
def spammer(bag=[]):
bag.append("spam")
return bag
spammer.__defaults__
OUTPUT:
([],)
Whenever we will call the function, the parameter bag will be assigned to the list object referenced by spammer.__defaults__[0]:
for i in range(5):
print(spammer())
print("spammer.__defaults__", spammer.__defaults__)
OUTPUT:
['spam'] ['spam', 'spam'] ['spam', 'spam', 'spam'] ['spam', 'spam', 'spam', 'spam'] ['spam', 'spam', 'spam', 'spam', 'spam'] spammer.__defaults__ (['spam', 'spam', 'spam', 'spam', 'spam'],)
Now, you know and understand what is going on, but you may ask yourself how to overcome this problem. The solution consists in using the immutable value None as the default. This way, the function can set bag dynamically (at run-time) to an empty list:
def spammer(bag=None):
if bag is None:
bag = []
bag.append("spam")
return bag
for i in range(5):
print(spammer())
print("spammer.__defaults__", spammer.__defaults__)
OUTPUT:
['spam'] ['spam'] ['spam'] ['spam'] ['spam'] spammer.__defaults__ (None,)
Live Python training
See our Python training courses
Docstring
The first statement in the body of a function is usually a string statement called a Docstring, which can be accessed with the function_name.__doc__. For example:
def hello(name="everybody"):
""" Greets a person """
print("Hello " + name + "!")
print("The docstring of the function hello: " + hello.__doc__)
OUTPUT:
The docstring of the function hello: Greets a person
Keyword Parameters
Using keyword parameters is an alternative way to make function calls. The definition of the function doesn't change. An example:
def sumsub(a, b, c=0, d=0):
return a - b + c - d
print(sumsub(12, 4))
print(sumsub(42, 15, 