Chapter 9. Functions – The Quick Python Book

Chapter 9. Functions

This chapter covers

  • Defining functions
  • Using function parameters
  • Passing mutable objects as parameters
  • Understanding local and global variables
  • Creating and using lambda expressions
  • Using decorators

This chapter assumes you’re familiar with function definitions in at least one other computer language and with the concepts that correspond to function definitions, arguments, parameters, and so forth.

9.1. Basic function definitions

The basic syntax for a Python function definition is

def name(parameter1, parameter2, . . .):
body

As it does with control structures, Python uses indentation to delimit the body of the function definition. The following simple example puts the factorial code from a previous section into a function body, so we can call a fact function to obtain the factorial of a number:

The second line is an optional documentation string or docstring. You can obtain its value by printing fact.__doc__. The intention of docstrings is to describe the external behavior of a function and the parameters it takes, whereas comments should document internal information about how the code works. Docstrings are strings that immediately follow the first line of a function definition and are usually triple quoted to allow for multiline descriptions. Browsing tools are available that extract the first line of document strings. It’s a standard practice for multiline documentation strings to give a synopsis of the function in the first line, follow this with a blank second line, and end with the rest of the information. This line shows the value after the return is sent back to the code calling the function .

 

Procedure or function?

In some languages, a function that doesn’t return a value is called a procedure. Although you can (and will) write functions that don’t have a return statement, they aren’t really procedures. All Python procedures are functions; if no explicit return is executed in the procedure body, then the special Python value None is returned, and if return arg is executed, then the value arg is immediately returned. Nothing else in the function body is executed once a return has been executed. Because Python doesn’t have true procedures, we’ll refer to both types as functions.

 

Although all Python functions return values, it’s up to you whether a function’s return value is used:

The return value isn’t associated with a variable . The fact function’s value is printed in the interpreter only . The return value is associated with the variable x .

9.2. Function parameter options

Most functions need parameters, and each language has its own specifications for how function parameters are defined. Python is flexible and provides three options for defining function parameters. These are outlined in this section.

9.2.1. Positional parameters

The simplest way to pass parameters to a function in Python is by position. In the first line of the function, you specify definition variable names for each parameter; when the function is called, the parameters used in the calling code are matched to the function’s parameter variables based on their order. The following function computes x to the power of y:

>>> def power(x, y):
... r = 1
... while y > 0:
... r = r * x
... y = y - 1
... return r
...
>>> power(3, 3)
27

This method requires that the number of parameters used by the calling code exactly match the number of parameters in the function definition, or a TypeError exception will be raised:

>>> power(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() takes exactly 2 positional arguments (1 given)
>>>

 

Default values

Function parameters can have default values, which you declare by assigning a default value in the first line of the function definition, like so:

def fun(arg1, arg2=default2, arg3=default3, . . .)

Any number of parameters can be given default values. Parameters with default values must be defined as the last parameters in the parameter list. This is because Python, like most languages, pairs arguments with parameters on a positional basis. There must be enough arguments to a function that the last parameter in that function’s parameter list that doesn’t have a default value gets an argument. See the next section, “Passing arguments by parameter name,” for a more flexible mechanism.

 

The following function also computes x to the power of y. But if y isn’t given in a call to the function, the default value of 2 is used, and the function is just the square function:

>>> def power(x, y=2):
... r = 1
... while y > 0:
... r = r * x
... y = y - 1
... return r
...

You can see the effect of the default argument in the following interactive session:

>>> power(3, 3)
27
>>> power(3)
9

9.2.2. Passing arguments by parameter name

You can also pass arguments into a function using the name of the corresponding function parameter, rather than its position. Continuing with the previous interactive example, we can type

>>> power(2, 3)
8
>>> power(3, 2)
9
>>> power(y=2, x=3)
9

Because the arguments to power in the final invocation of it are named, their order is irrelevant; the arguments are associated with the parameters of the same name in the definition of power, and we get back 3^2. This type of argument passing is called keyword passing.

Keyword passing, in combination with the default argument capability of Python functions, can be highly useful when you’re defining functions with large numbers of possible arguments, most of which have common defaults. For example, consider a function that’s intended to produce a list with information about files in the current directory and that uses Boolean arguments to indicate whether that list should include information such as file size, last modified date, and so forth, for each file. We can define such a function along these lines

def list_file_info(size=False, create_date=False, mod_date=False, ...):
...get file names...
if size:
# code to get file sizes goes here
if create_date:
# code to get create dates goes here
.
.
.
return fileinfostructure

and then call it from other code using keyword argument passing to indicate that we want only certain information (in this example, the file size and modification date but not the creation date):

fileinfo = list_file_info(size=True, mod_date=True)

This type of argument handling is particularly suited for functions with very complex behavior, and one place such functions occur is in graphical user interfaces. If you ever use the Tkinter package to build GUIs in Python, you’ll find that the use of optional, keyword-named arguments like this is invaluable.

9.2.3. Variable numbers of arguments

Python functions can also be defined to handle variable numbers of arguments. You can do this two different ways. One way handles the relatively familiar case where you wish to collect an unknown number of arguments at the end of the argument list into a list. The other method can collect an arbitrary number of keyword-passed arguments, which have no correspondingly named parameter in the function parameter list, into a dictionary. These two mechanisms are discussed next.

Dealing with an indefinite number of positional arguments

Prefixing the final parameter name of the function with a * causes all excess non-keyword arguments in a call of a function (that is, those positional arguments not assigned to another parameter) to be collected together and assigned as a tuple to the given parameter. Here’s a simple way to implement a function to find the maximum in a list of numbers.

First, implement the function:

>>> def maximum(*numbers):
... if len(numbers) == 0:
... return None
... else:
... maxnum = numbers[0]
... for n in numbers[1:]:
... if n > maxnum:
... maxnum = n
... return maxnum
...

Now, test out the behavior of the function:

>>> maximum(3, 2, 8)
8
>>> maximum(1, 5, 9, -2, 2)
9
Dealing with an indefinite number of arguments passed by keyword

An arbitrary number of keyword arguments can also be handled. If the final parameter in the parameter list is prefixed with **, it will collect all excess keyword-passed arguments into a dictionary. The index for each entry in the dictionary will be the keyword (parameter name) for the excess argument. The value of that entry is the argument itself. An argument passed by keyword is excess in this context if the keyword by which it was passed doesn’t match one of the parameter names of the function.

For example:

>>> def example_fun(x, y, **other):
... print("x: {0}, y: {1}, keys in 'other': {2}".format(x,
... y, list(other.keys())))
... other_total = 0
... for k in other.keys():
... other_total = other_total + other[k]
... print("The total of values in 'other' is {0}".format(other_total))

Trying out this function in an interactive session reveals that it can handle arguments passed in under the keywords foo and bar, even though these aren’t parameter names in the function definition:

>>> example_fun(2, y="1", foo=3, bar=4)
x: 2, y: 1, keys in 'other': ['foo', 'bar']
The total of values in 'other' is 7

9.2.4. Mixing argument-passing techniques

It’s possible to use all of the argument-passing features of Python functions at the same time, although it can be confusing if not done with care. Rules govern what you can do. See the documentation for the details.

9.3. Mutable objects as arguments

Arguments are passed in by object reference. The parameter becomes a new reference to the object. For immutable objects (such as tuples, strings, and numbers), what is done with a parameter has no effect outside the function. But if you pass in a mutable object (for example, a list, dictionary, or class instance), any change made to the object will change what the argument is referencing outside the function. Reassigning the parameter doesn’t affect the argument, as shown in figures 9.1 and 9.2:

>>> def f(n, list1, list2):
... list1.append(3)
... list2 = [4, 5, 6]
... n = n + 1
...
>>> x = 5
>>> y = [1, 2]
>>> z = [4, 5]
>>> f(x, y, z)
>>> x, y, z
(5, [1, 2, 3], [4, 5])
Figure 9.1. At the beginning of function f(), both the initial variables and the function parameters refer to the same objects.

Figure 9.2. At the end of function f(), y (list1 inside the function) has been changed internally, whereas n and list2 refer to different objects.

Figures 9.1 and 9.2 illustrate what happens when function f is called. The variable x isn’t changed because it’s immutable. Instead, the function parameter n is set to refer to the new value of 6. Likewise, variable z is unchanged because inside function f, its corresponding parameter list2 was set to refer to a new object, [4, 5, 6]. Only y sees a change because the actual list it points to was changed.

9.4. Local, nonlocal, and global variables

Let’s return to our definition of fact from the beginning of this chapter:

def fact(n):
"""Return the factorial of the given number."""
r = 1
while n > 0:
r = r * n
n = n - 1
return r

Both the variables r and n are local to any particular call of the factorial function; changes to them made when the function is executing have no effect on any variables outside the function. Any variables in the parameter list of a function, and any variables created within a function by an assignment (like r = 1 in fact), are local to the function.

You can explicitly make a variable global by declaring it so before the variable is used, using the global statement. Global variables can be accessed and changed by the function. They exist outside the function and can also be accessed and changed by other functions that declare them global or by code that’s not within a function. Let’s look at an example to see the difference between local and global variables:

>>> def fun():
... global a
... a = 1
... b = 2
...

This defines a function that treats a as a global variable and b as a local variable and attempts to modify both a and b.

Now, test this function:

>>> a = "one"
>>> b = "two"
>>> fun()
>>> a
1
>>> b
'two'

The assignment to a within fun is an assignment to the global variable a also existing outside of fun. Because a is designated global in fun, the assignment modifies that global variable to hold the value 1 instead of the value "one". The same isn’t true for b—the local variable called b inside fun starts out referring to the same value as the variable b outside of fun, but the assignment causes b to point to a new value that’s local to the function fun.

Similar to the global statement is the nonlocal statement, which causes an identifier to refer to a previously bound variable in the closest enclosing scope. We’ll discuss scopes and namespaces in more detail in the next chapter, but the point is that global is used for a top-level variable, whereas nonlocal can refer to any variable in an enclosing scope, as the example in listing 9.1 illustrates.

Listing 9.1. File nonlocal.py

When run, this code prints the following:

top level-> g_var: 0 nl_var: 0
in test-> g_var: 0 nl_var: 2
in inner_test-> g_var: 1 nl_var: 4
in test-> g_var: 1 nl_var: 4
top level-> g_var: 1 nl_var: 0

Note that the value of the top-level nl_var hasn’t been affected, which would happen if inner_test contained the line global nl_var.

The bottom line is that if you want to assign to a variable existing outside a function, you must explicitly declare that variable to be nonlocal or global. But if you’re accessing a variable that exists outside the function, you don’t need to declare it nonlocal or global. If Python can’t find a variable name in the local function scope, it will attempt to look up the name in the global scope. Hence, accesses to global variables will automatically be sent through to the correct global variable. Personally, I don’t recommend using this shortcut. It’s much clearer to a reader if all global variables are explicitly declared as global. Further, you probably want to limit the use of global variables within functions to only rare occasions.

9.5. Assigning functions to variables

Functions can be assigned, like other Python objects, to variables, as shown in the following example:

You can place them in lists, tuples, or dictionaries:

A variable that refers to a function can be used in exactly the same way as the function . This last example shows how you can use a dictionary to call different functions by the value of the strings used as keys. This is a common pattern in situations where different functions need to be selected based on a string value, and in many cases it takes the place of the switch structure found in languages like C and Java.

9.6. lambda expressions

Short functions like those you just saw can also be defined using lambda expressions of the form

lambda parameter1, parameter2, . . .: expression

lambda expressions are anonymous little functions that you can quickly define inline. Often, a small function needs to be passed to another function, like the key function used by a list’s sort method. In such cases, a large function is usually unnecessary, and it would be awkward to have to define the function in a separate place from where it’s used. Our dictionary in the previous subsection can be defined all in one place with

This defines lambda expressions as values of the dictionary . Note that lambda expressions don’t have a return statement, because the value of the expression is automatically returned.

9.7. Generator functions

A generator function is a special kind of function that you can use to define your own iterators. When you define a generator function, you return each iteration’s value using the yield keyword. When there are no more iterations, an empty return statement or flowing off the end of the function ends the iterations. Local variables in a generator function are saved from one call to the next, unlike in normal functions:

Note that this generator function has a while loop that limits the number of times the generator will execute. Depending on how it’s used, a generator that doesn’t have some condition to halt it could cause an endless loop when called.

You can also use generator functions with in to see if a value is in the series that the generator produces:

>>> 2 in four()
in generator, x = 0
in generator, x = 1
in generator, x = 2
True
>>> 5 in four()
in generator, x = 0
in generator, x = 1
in generator, x = 2
in generator, x = 3
False

9.8. Decorators

Because functions are first-class objects in Python, they can be assigned to variables, as you’ve seen. Functions can be passed as arguments to other functions and passed back as return values from other functions.

For example, it’s possible to write a Python function that takes another function as its parameter, wrap it in another function that does something related, and then return the new function. This new combination can be used instead of the original function:

>>> def decorate(func):
... print("in decorate function, decorating", func.__name__)
... def wrapper_func(*args):
... print("Executing", func.__name__)
... return func(*args)
... return wrapper_func
...
>>> def myfunction(parameter):
... print(parameter)
...
>>> myfunction = decorate(myfunction)
in decorate function, decorating myfunction
>>> myfunction("hello")
Executing myfunction
hello

A decorator is syntactic sugar for this process and lets you wrap one function inside another with a one-line addition. This still gives you exactly the same effect as the previous code, but the resulting code is much cleaner and easier to read.

Very simply, using a decorator involves two parts: defining the function that will be wrapping or “decorating” other functions and then using an @ followed by the decorator immediately before the wrapped function is defined. The decorator function should take a function as a parameter and return a function, as follows:

The decorate function prints the name of the function it’s wrapping when the function is defined . When it’s finished, the decorator returns the wrapped function . myfunction is decorated using @decorate . The wrapped function is called after the decorator function has completed .

Using a decorator to wrap one function in another can be handy for a number of purposes. In web frameworks such as Django, decorators are used to make sure a user is logged in before executing a function; and in graphics libraries, decorators can be used to register a function with the graphics framework.

9.9. Summary

Defining functions in Python is simple but highly flexible. Although all variables created during the execution of a function body are local to that function, external variables can easily be accessed using the global statement.

Python functions provide exceedingly powerful argument-passing features:

  • Arguments may be passed by position or by parameter name.
  • Default values may be provided for function parameters.
  • Functions can collect arguments into tuples, giving you the ability to define functions that take an indefinite number of arguments.
  • Functions can collect arguments into dictionaries, giving you the ability to define functions that take an indefinite number of arguments passed by parameter name.

Functions are first-class objects in Python, which means that they can be assigned to variables, accessed by way of variables, and decorated. Functions are essential building blocks for writing readable, structured code. By packaging code that performs a particular function, they make reusing that code easier, and they also make the rest of your code simpler and easier to understand. The next step along this path is packaging functions (and other objects) into modules, which is the topic of the next chapter.