Introduction to Python Programming. Section 8. Functions
8 Functions
8.1 Objectives
In this section you will learn
- How to define and call functions.
- Why it is important to write docstrings.
- About the difference between parameters and arguments.
- How to return multiple values.
- What one means by a wrapper.
- How to use parameters with default values.
- How to accept a variable number of arguments.
- How to define local functions within functions.
- About anonymous lambda functions.
- That functions can create and return other functions.
- That functions are objects, and how to take advantage of that.
8.2 Why learn about functions?
The purpose of writing functions is to make selected functionality easily reusable throughout the code. In this way one avoids code duplication, and brings more structure and transparency into the software.
8.3 Defining new functions
In Python, the definition of every new function begins with the keyword def, but one
moreover has to add round brackets and the colon ’:’ at the end of the line. The following
sample function adds two numbers and returns the result:
The round brackets in the function definition are mandatory even if the function does not
have any input parameters, but the return statement can be omitted if not needed.
Lines 2 - 4 contain a docstring. Docstrings are optional, but providing one with
every function is very important. We will explain why in the following Subsection
8.4.
Note that the code above contains a function definition only – the function is not called. In
other words, if you run that program, nothing will happen. In order to call the
function and add some numbers with it, one needs to write one additional line such
as:
This will produce the following output:
In most cases, functions are defined to process some input parameters and to return some
output values. However, this does not apply always. The following function does not take
any arguments and it does not return anything:
8.4 Docstrings and function help
Every function should have a docstring – concise and accurate description of what it does. This is useful not only for other people who look at your software, but also for you. There is a famous saying that when you see your own code after several moths, it might as well be written by somebody else – and there is something to it. Showing other people a code where functions do not have docstrings will lead them to think that you probably are not a very experienced programmer.
In addition, Python has a built-in function help which displays the docstrings. This is
incredibly useful in larger software projects where usually you do not see the source code of
all functions that you are using. Here is a simple example:
8.5 Function parameters vs. arguments
The words "parameter" and "argument" are often confused by various authors, so let’s clarify
them. When we talk about the definition of a function, such as
then we say that the function add has two parameters a and b. But when we are talking about
calling the function, such as
then we say that the function was called with the arguments x and y.
8.6 Names of functions should reveal their purpose
You already know from Section 5 that Python is a dynamically (weakly) typed language. This means that one does not have to specify the types of newly created variables.
Analogously, one does not have to specify the types of function parameters. This means
that the above function add(a, b) will work not only for numbers but also for text
strings, lists, and any other objects where the operation ’+’ is defined. Let’s try
this:
Or this:
In the last two examples, the use of the function was incompatible with its original purpose which is described in the docstring. Statically typed languages such as C, C++ or Java are more strict and this is not possible. As usual, Python gives you plenty of freedom. Do not abuse it. Reading your code should not be an intellectual exercise. On the contrary – by looking at your code, the reader should effortlessly figure out what the code is doing. For this reason, instead of using a "smart" universal function such as the function add defined above, it would be better to define separate functions add_numbers, add_strings and add_lists whose names clearly state what they are meant to do.
8.7 Functions returning multiple values
Python functions can return multiple values which often comes very handy. For example, the
following function time decomposes the number of seconds into hours, minutes and
seconds, and returns them as three values:
Technically, the returned comma-separated values h, m, s are a tuple, so the function could
be written with (h, m, s) on the last line:
The tuple which is returned from the function can be stored in a single variable which then
becomes a tuple:
Or the returned tuple can be stored in three separate variables:
8.8 What is a wrapper?
Sometimes, when looking for some answers on the web, you may come across the word wrapper. By a wrapper one usually means a function that "wraps" around another function – meaning that it does not do much besides just calling the original function, but adding or changing a few things. Wrappers are often used for functions which one cannot or does not want to change directly. Let’s show an example.
In Subsection 4.33 you learned how to use the ctime function of the time library to
extract the current date and time from the system. The 24-character-long text string contains a
lot of information:
But what if one only needs to extract the date? Here is a wrapper for the function
time.ctime which does that:
Another example of a simple wrapper will be shown in the next subsection. Later, in Subsection 18.1, we will take wrappers to the next level.
8.9 Using parameters with default values
Have you ever been to Holland? It is the most bicycle friendly place in the world. Imagine
that you work for the Holland Census Bureau. Your job is to ask 10000 people how they
go to work, and enter their answers into a database. The program for entering
data into the database was written by one of your colleagues, and it can be used as
follows:
or
or
etc. Since you are in Holland, it can be expected that 99% of people are using the bicycle. In principle you could call the function add_database_entry() to enter each answer, but with 9900 bicyclists out of 10000 respondents you would have to type the word "bicycle" many times.
Fortunately, Python offers a smarter way to do this. One can define a new function
This is a simple (thin) wrapper to the function add_database_entry() that allows us to
omit the third argument in the function call and autocomplete it with a default value which is
~bicycle~. In other words, now we do not have to type ~bicycle~ for all the
bicyclists:
Only if we meet a rare someone who uses a car, we can type
A few things to keep in mind:
(1) Parameters with default values need to be introduced after standard (non-default)
parameters. In other words, the following code will result into an error:
(2) When any of the optional arguments are present in the function call, it is a good practise
to use their names to avoid ambiguity and make your code easier to read:
In this case the value 1 will be assigned to x, a will take the default value 2, and b will be 5.
The output will be
8.10 Functions accepting a variable number of arguments
The following function will multiply two numbers:
But what if we wanted a function that can by called as multiply(2, 3, 6) or
multiply(2, 3, 6, 9), multiplying a different number of values each time?
Well, the following could be done using a list:
But then one would have to use extra square brackets when calling the function. For two
values, the code multiply([2, 3]) would not be backward compatible with the original
code multiply(2, 3).
This problem can be solved using *args. Using *args is very similar to using a
list, but one does not need to use the square brackets when calling the function
afterwards:
8.11 *args is not a keyword
As a remark, let us mention that args is just a name for a tuple, and any other name would
work as well:
However, using the name args in functions with a variable number of arguments is so common that using a different name might confuse a less experienced programmer. Therefore, we recommend to stay with args.
8.12 Passing a list as *args
When using *args, then args inside the function is a tuple (immutable type), not a list. So,
using *args is safer from the point of view that the function could change the list, but it
cannot change the args.
When we have many numbers which we don’t want to type one after another,
or when we want to pass a list L as *args for another reason, it can be done by
passing *L to the function instead of L. The following example multiplies 1000
numbers:
8.13 Obtaining the number of *args
For some tasks one needs to know the number of the arguments which are passed through
*args. Since args is a tuple inside the function, one can normally use the function len. This
is illustrated on the function average below which calculates the arithmetic average of an
arbitrary number of values:
8.14 Combining standard arguments and *args
Let’s say that we need a function which not only calculates the average of an arbitrary
number of values, but in addition increases or decreases the result by a given offset. The
easiest way is to put the standard parameter first, and *args second:
You could also do it the other way round – put *args first and the offset second
– but this is not natural and people would probably ask why you did it. When
calling the function, you would have to keyword the standard argument to avoid
ambiguity:
8.15 Using keyword arguments *kwargs
The name kwargs stands for "keyword arguments". You already know from Subsection 8.10 that args is a tuple inside the function. Analogously, kwargs is a dictionary. In other words, the **kwargs construct makes it possible to pass a dictionary into a function easily, without using the full dictionary syntax. In Subsection 8.11 we mentioned that args is just a name and one can use any other name instead. The same holds about kwargs.
For illustration, the following function displaydict accepts a standard dictionary D and
displays it, one item per line:
The code below does the same using **kwargs. Notice that instead of the colon : one uses
the assignment operator =. Also notice that the keys are not passed as text strings anymore –
they are converted to text strings implicitly:
8.16 Passing a dictionary as **kwargs
In Subsection 8.12 you have seen how one can pass a list to a function through *args.
Analogously, it is possible to pass a dictionary to a function through **kwargs, as we show
in the following example:
8.17 Defining local functions within functions
In Subsections 5.13 - 5.15 you learned about the importance of using local (as well as not using global) variables in functions. In short, defining variables on the global scope pollutes the entire code because the name of a global variable can clash with the definition of a function or variable defined elsewhere.
Using local functions is less common than using local variables. But if one needs to use
some functionality more than once within the body of a function, and not elsewhere in the
code, then one should create a local function for it. Typically, the local function would be a
small, one-purpose function while the outer function might be rather large and complex.
Here is a bit artificial example whose sole purpose is to illustrate the mechanics of
this:
~~~
This function splits a list of integers
into two lists of even and odd numbers.
~~~
def is_even(v):
~~~
This function returns True if the number n
is even, and False otherwise.
~~~
return v%2 == 0
E = []
O = []
for n in L:
if is_even(n):
E.append(n)
else:
O.append(n)
return E, O
We will not copy the entire code once more here, but if we tried to call the function is_even
outside the function split_numbers, the interpreter would throw an error message similar
to this one:
8.18 Anonymous lambda functions
Python has a keyword lambda which makes it possible to define anonymous one-line
functions. The most important thing about these functions is that they are expressions (in
contrast to standard functions defined using the def statement). Therefore, lambda functions
can be used where statements are not allowed. The following example defines a lambda
function which for a given value of x returns x**2, and then calls it with x = 5:
Note that the keyword lambda is followed by the name of the independent variable (here x), a colon, and then an expression containing the independent variable.
Also note that the anonymous function is assigned to a variable named g, by which it loses its anonymity. This negates the purpose of the keyword lambda and it’s not how lambda functions are normally used. We did it here for simplicity – just to be able to call the function and display the result without making things more complex right away.
Before getting to more realistic applications of anonymous functions, let’s mention that (in
rare cases) they can be defined without parameters:
And they can be defined with more than one parameter if needed:
8.19 Creating multiline lambdas
Although it is possible to create multi-line lambda functions, this is against the philosophy of Python and strongly discouraged. If you really must do it, search the web and you will find your example.
8.20 Using lambdas to create a live list of functions
Let’s say that you have a large data file D, and for each value v in it you need to calculate
v**2, cos(v), sin(v), exp(v) and log(v). Here is a very small sample of your data
file:
Then an elegant approach is to define a list containing the five functions,
and use a pair of nested for loops to apply all functions to all data points:
Output:
As you could see, in this example the five lambda functions were truly anonymous – their names were not needed because they were stored in a list.
8.21 Using lambdas to create a live table of functions
Imagine that you want to create a table (dictionary) of functions where on the left will be
their custom names and on the right the functions themselves as executable formulas. This
can be done as follows:
8.22 Using lambdas to create a live table of logic gates
Recall that there are seven types of basic logic gates:
- AND(a, b) = a and b,
- OR(a, b) = a or b,
- NOT(a) = not a,
- NAND(a, b) = not(AND(a, b)),
- NOR(a, b) = not(OR(a, b)),
- XOR(a, b) = (a and not b) or (not a and b),
- XNOR(a, b) = not(XOR(a, b)).
The previous example can easily be adjusted to create their table:
~~~
Add into dictionary d an arbitrary number of functions.
~~~
for name, f in kwargs.items():
d[name] = f
LG =
addfns(LG, AND=lambda a, b: a and b, OR=lambda a, b: a or b, \
NOT=lambda a: not a, NAND=lambda a, b: not (a and b), \
NOR=lambda a, b: not (a or b), \
XOR=lambda a, b: (a and not b) or (not a and b), \
XNOR=lambda a, b: not((a and not b) or (not a and b)))
x = True
y = True
print(LG[’AND’](x, y))
print(LG[’OR’](x, y))
print(LG[’NOT’](x))
print(LG[’NAND’](x, y))
print(LG[’NOR’](x, y))
print(LG[’XOR’](x, y))
print(LG[’XNOR’](x, y))
8.23 Using lambdas to create a factory of functions
We have not talked yet about functions which create and return other functions. This is a very natural thing in Python which has many applications. To mention just one - it allows Python to be used for functional programming. This is an advanced programming paradigm which is very different from both procedural and object-oriented programming. We will not discuss it in this course, but if you are interested, you can read more on Wikipedia (https://en.wikipedia.org/wiki/Functional_programming).
The following example defines a function quadfun(a, b, c) which returns a function
, and shows two different ways to call it:
Standard functions which are defined locally within other functions can be returned in the same way.
8.24 Variables of type ’function’
In the previous example, we created a quadratic function and assigned it to a
variable named f. Hence this variable is callable (has type ’function’). You already know
from Subsection 5.11 how to check the type of a variable using the built-in function
type:
Python has a built-in function callable which can be used to check at runtime whether an
object is a function:
8.25 Inserting conditions into lambda functions
Look at the sample functions min, even and divisible:
Here, if-else is not used as a statement, but as a conditional expression (ternary operator).
We will talk more about this in Subsection 10.10. For now, the above examples give you an
idea of how to incorporate conditions into lambda functions.
Notice that we named the anonymous functions right after creating them, which contradicts their purpose. We just did this here to illustrate the inclusion of the conditional statement without making things unnecessarily complex.
8.26 Using lambdas to customize sorting
As you know from Subsection 7.22, calling L.sort() will sort the list L in place. From Subsection 7.23 you also know that Python has a built-in function sorted which returns a sorted copy of the list L while leaving L unchanged. Both sort and sorted accept an optional argument key which is an anonymous function.
This function, when provided, is applied to all items in the list L prior to sorting. It creates a helper list. The helper list is then sorted, and the original list L simultaneously with it. This makes it possible to easily implement many different sorting criteria.
Let’s look at an example that sorts a list of names in the default way:
But it would be much better to sort the names according to the last name. This can be done by
defining a key which splits each text string into a list of words, and picks the last
item:
8.27 Obtaining useful information about functions from their attributes
Functions in Python are objects, which means that they have their own attributes (more about this will be said in Section 14). Three of the most widely used ones are __name__ which contains the function’s name, __doc__ which contains the docstring (if defined), and __module__ which contains the module where the function is defined.
Let’s look, for example, at the built-in function sorted:
This works in the same way for your custom functions (as long as the docstring is defined).
Table of Contents
- Preface
- 1. Introduction
- 2. Using Python as a Scientific Calculator
- 3. Drawing, Plotting, and Data Visualization with Matplotlib
- 4. Working with Text Strings
- 5. Variables and Types
- 6. Boolean Values, Functions, Expressions, and Variables
- 7. Lists, Tuples, Dictionaries, and Sets
- 8. Functions
- 9. The ’For’ Loop
- 10. Conditions
- 11. The ’While’ Loop
- 12. Exceptions
- 13. File Operations
- 14. Object-Oriented Programming I – Introduction
- 15. Object-Oriented Programming II – Class Inheritance
- 16. Object-Oriented Programming III – Advanced Aspects
- 17. Recursion
- 18. Decorators
- 19. Selected Advanced Topics