Functions
Contents
%matplotlib inline
import matplotlib.pyplot as plt
from myturtle import Turtle
Functions¶
In one of the previous examples we have seen that data, which is used at different parts of the code, should be represented by a named variable. The same holds for code structures which repeatedly process the same or similar data. This can be achieved by defining functions. It prevents redundancy in the code and makes the code more readable and maintainable.
Defining functions¶
Every definition of a function starts with the function signiture. The signature starts with the keyword def
, followed by the name of the function and the definition of possible arguments in round brackets, and ends with a colon. The signature is followed by the indented body of the function, consisting of at least one command.
def hello_world():
print("Hello world")
After a function is defined, it can be called. It is important to use the round brackets, even if the function does not need any argument.
hello_world()
Hello world
Functions always return a value. This value can be defined with the keyword return
. If no return value is defined, the function returns None
.
The following function does not print the string “Hello world” but it will return it when called.
def hello_world_string():
return 'Hello world'
It is also possible to return multiple objects by writing them as tuples:
def give_words():
return "ham", "spam", "eggs"
words = give_words() # This is a tuple with three elements
w1, w2, w3 = give_words() # Directly unpackes the tuple
An important task in programming is the documentation of your code, that is to describe in words, what it does and how one uses it. In Python, this is done by writing docstrings into the code. For functions, the docstring is a string at the beginning of the function body. Usually it is a multiline string , enclosed by three quotation marks """
. This string is returned by the help()
function.
def hello_world_string():
"""Returns the string "Hello World"."""
s = "Hello World"
return s
help(hello_world_string)
# or
hello_world_string?
Help on function hello_world_string in module __main__:
hello_world_string()
Returns the string "Hello World".
Signature: hello_world_string()
Docstring: Returns the string "Hello World".
File: ~/Documents/applied-programming-book/content/basics/<ipython-input-13-44357a835bb6>
Type: function
When a function is defined, it is an object as any other variable. Thus it can be allocated to another variable or passed to other functions as an argument.
Arguments and Parameters¶
Functions are used to execute a once defined sequence of tasks on some set of data that can be different from execution to execution. This data is passed to the function via arguments when calling the function. The names of the arguments that are used within the function body are called parameters and are defined in the round brackets within the function signature. For Python, arguments do not have a defined type, it can change during the runtime of the program. Hence, the language is dynamically typed.
When calling a function in Python, arguments can either be passed as positional arguments or keyword arguments.
Positional arguments¶
Positional arguments are used by simply giving the value of the argument when calling a function. The example below uses positional arguments when calling the function greet
. Note that the type of the arguments differ in both calls to the function.
def greet(name):
print("Hello {}".format(name))
greet("Martin")
greet(4)
Hello Martin
Hello 4
A function can also take multiple arguments. The arguments and the corresponding parameters are given as a comma separated list in the function call and its’ signature, respectively.
def div(numerator, denominator):
return numerator / denominator
print(div(5, 7))
print(div(7, 5))
0.7142857142857143
1.4
The example above illustrates why it is called positional arguments. It is the position of the values in the function call that determines which variables they are assigned to in the function body.
Keyword arguments¶
Arguments can also be passed as key=value
pairs which enhances the readability of the code. Using keyword arguments allows to neglect the order of arguments as given by the function signature.
def div(numerator, denominator):
return numerator / denominator
print(div(numerator=3, denominator=6.))
print(div(denominator=6., numerator=3))
0.5
0.5
We can also mix the use of positional and keyword arguments. However, we then have to put the positional arguments first followed by the keyword arguments. In any case, the association between the arguments and parameters must be unambiguous.
print(div(3, denominator=6))
print(div(nominator=3, 6)) ## this is a syntax error
File "<ipython-input-28-50a9eacaf465>", line 2
print(div(nominator=3, 6)) ## this is a syntax error
^
SyntaxError: positional argument follows keyword argument
print(div(3, nominator=6)) # this is a type error
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-27-7691ef50def9> in <module>
----> 1 print(div(3, nominator=6)) # this is a type error
TypeError: div() got an unexpected keyword argument 'nominator'
Default values¶
Often one would like to define a default value for a certain parameter. This can be done with an assignment in the function signature. When calling the function, we may then omit the argument that has a default value defined.
def greet(name, msg="How do you do?"):
"""
This function greets to the person with the provided message.
If message is not provided, it defaults to "How do you do?"
"""
print("Hello {}. {}".format(name, msg))
greet(name="Bruce", msg="Good morning!")
greet(name="Bruce")
greet("Bruce", msg="Good morning!")
Hello Bruce. Good morning!
Hello Bruce. How do you do?
Hello Bruce. Good morning!
To have an unambiguous map between arguments and parameters all parameters having a default value must be at the end of the parameter list in the function signature.
def div(a=1, b):
return a / b
File "<ipython-input-32-83f329bb83cb>", line 1
def div(a=1, b):
^
SyntaxError: non-default argument follows default argument
Note
The evaluation of the assignment expression in the function signature happens only once when the interpreter reads the function signature.
a = 5
def sum_5(n1, n2=a):
return n1 + n2
print(sum_5(1))
a = 6
print(sum_5(1))
6
6
Warning
When a mutable
data type is used as the default value a reference is set to it. Hence, the default value can change during runtime which is commonly considered undesired behavior! Thus never use a mutable
data type as a default!
a = [1]
def append_element(e, l=a):
return l + [e]
print(append_element(5))
print(append_element(6))
print(a)
a[0]=6
print(append_element(5))
[1, 5]
[1, 6]
[1]
[6, 5]
Variable number of parameters¶
It is possible to define a function which accepts a variable number of arguments. To have access to these variable parameters, there is the *
and **
notation in the function signature. *
is used for positional arguments and a **
is used for keyword arguments and these are always at the end of the parameter list.
def func_pos_args(*args):
print(args)
def func_kw_args(**kwargs):
print(kwargs)
def func_general_varargs(arg1, arg2, arg3='some value', *args, **kwargs):
print(arg1)
print(arg2)
print(arg3)
print(args)
print(kwargs)
In the examples above, args
is a tuple of all additional positional arguments that have been passed to the function. kwargs
is a dictionary mapping the keys to the values of all additional keyword arguments given in the function call. Additional here means that these arguments do not match a parameter that is explicitly defined in the function signature.
Using an analogous notation, a sequence or a dictionary of arguments can be passed to a function.
def sum(a, b):
return a + b
numbers = (4, 5)
print(sum(*numbers))
numbers_dict = {'a': 4, 'b': 5}
print(sum(**numbers_dict))
9
9
This allows, for example, the definition of function wrappers. The following function wrapper
accepts any function as first argument and calls it after the print
command with the given arguments:
def wrapper(func, *args, **kwargs):
print(
"Function {} executed with Arguments {} and {}".format(
func.__name__, args, kwargs
)
)
return_value = func(*args, **kwargs)
return return_value
wrapper(sum, 2, b=3)
Function sum executed with Arguments (2,) and {'b': 3}
5
Scope¶
The scope defines the visibility of a name (object) within a block of code. Names which are used in their own scope are called local. Names from an enclosing scope are called global. The local scope always supersedes the global scope. This means for functions:
Names that are defined outside the function body are visible within the function body as global variables.
def f():
print(s)
s = 'I love Paris!'
f()
s = 'I love London!'
f()
I love Paris!
I love London!
Names that are defined within the function body, are not visible outside.
def f(city):
s2 = "I love {}".format(city)
print(s2)
f('Paris')
print(s2) # This throws a name error
I love Paris
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-44-b3ce25f1140c> in <module>
4
5 f('Paris')
----> 6 print(s2) # This throws a name error
NameError: name 's2' is not defined
Is a name defined both inside and outside, the local definition supersedes the global within the function body.
s3 = "I hate {}"
def f(city):
s3 = "I love {}"
print(s3.format(city))
f('Paris')
print(s3.format('Paris'))
I love Paris
I hate Paris
Note
In principal it is possible to change global variables within the function body, if they are
mutable
. Such a function is called to have side effects. Usually this is not desired, since this can lead to errors, that are hard to identify. This also applies tomutable
arguments passed to a function.Python also offers the possibility to change global
immutable
variables within the function body. Therefore the keywordglobal
is introduced. For the same reasons as above, this is not recommended!