In our lust for measurement, we frequently measure that which we can rather than that which we wish to measure…and forget that there is a difference.
George Udny Yule
In This Chapter
The last and perhaps most powerful compound statement that we discuss is the function. Functions give you a way to name a code block wrapped as an object. That code can then be invoked by use of that name, allowing the same code to be called multiple times and in multiple places.
A function definition defines a function object, which wraps the executable block. The definition does not run the code block but just defines the function. The definition describes how the function can be called, what it is named, what parameters can be passed to it, and what will be executed when it is invoked. The building blocks of a function are the controlling statement, an optional docstring, the controlled code block, and a return statement.
The first line of a function definition is the control statement, which takes the following form:
def <Function Name> (<Parameters>):
The def
keyword indicates a function definition, <Function Name>
is where the name that will be used to call the function is defined, and <Parameters>
is where any arguments that can be passed to the function are defined. For example, the following function is defined with the name do_nothing
and a single parameter named not_used
:
def do_nothing(not_used): pass
The code block in this case consists of a single pass
statement, which does nothing. The Python style guide, PEP8, has conventions for naming functions (see https://www.python.org/dev/peps/pep-0008/#function-and-variable-names).
The next part of a function definition is the documentation string, or docstring, which contains documentation for the function. It can be omitted, and the Python compiler will not object. However, it is highly recommended to supply a docstring for all but the most obvious methods. The docstring communicates your intentions in writing a function, what the function does, and how it should be called. PEP8 provides guidance regarding the content of docstrings (see https://www.python.org/dev/peps/pep-0008/#documentation-strings). The docstring consists of a single-line string or a multiline string surrounded in three pairs of double quotes that immediately follows the control statement:
def do_nothing(not_used): """This function does nothing.""" pass
For a single-line docstring, the quotes are on the same line as the text. For a multiline docstring, the quotes are generally above and below the text, as in Listing 6.1.
def do_nothing(not_used): """ This function does nothing. This function uses a pass statement to avoid doing anything. Parameters: not_used - a parameter of any type, which is not used. """ pass
The first line of the docstring should be a statement summarizing what the function does. With a more detailed explanation, a blank line is left after the first statement. There are many different possible conventions for what is contained after the first line of a docstring, but generally you want to offer an explanation of what the function does, what parameters it takes, and what it is expected to return. The docstring is useful both for someone reading your code and for various utilities that read and display either the first line or the whole docstring. For example, if you call the help()
function on the function do_nothing()
, the docstring is displayed as shown in Listing 6.2.
help(do_nothing) Help on function do_nothing in module __main__: do_nothing(not_used) This function does nothing. This function uses a pass statement to avoid doing anything. Parameters: not_used - a parameter of any type, which is not used.
Parameters allow you to pass values into a function, which can be used in the function’s code block. A parameter is like a variable given to a function when it is called, where the parameter can be different every time you call the function. A function does not have to accept any parameters. For a function that should not accept parameters, you leave the parentheses after the function name empty:
def no_params(): print("I don't listen to nobody")
When you call a function, you pass the values for the parameters within the parentheses following the function name. Parameter values can be set based on the position at which they are passed or based on keywords. Functions can be defined to require their parameters be passed in either or a combination of these ways. The values passed to a function are attached to variables with the names defined in the function definition. Listing 6.3 defines three parameters: first
, second
, and third
. These variables are then available to the code block that follows, which prints out the values for each parameter.
def does_order(first, second, third): '''Prints parameters.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') does_order(1, 2, 3) First: 1 Second: 2 Third: 3 does_order(first=1, second=2, third=3) First: 1 Second: 2 Third: 3 does_order(1, third=3, second=2) First: 1 Second: 2 Third: 3
Listing 6.3 defines the function does_order()
and then calls it three times. The first time, it uses the position of the arguments, (1, 2, 3)
, to assign the variable values. It assigns the first value to the first parameter, first
, the second value to the second parameter, second
, and the third value to the third parameter, third
.
The second time the listing calls the function does_order()
, it uses keyword assignment, explicitly assigning the values using the parameter names,
(first=1, second=2, third=3)
. In the third call, the first parameter is assigned by position, and the other two are assigned using keyword assignment. Notice that in all three cases, the parameters are assigned the same values.
Keyword assignments do not rely on the position of the keywords. For example, you can assign third=3
in the position before second=2
without issue. You cannot use a keyword assignment to the left of a positional assignment, however:
does_order(second=2, 1, 3) File "<ipython-input-9-eed80203e699>", line 1 does_order(second=2, 1, 3) ^ SyntaxError: positional argument follows keyword argument
You can require that a parameter be called only using the keyword method by putting a *
to its left in the function definition. All parameters to the right of the star can only be called using keywords. Listing 6.4 shows how to make the parameter third
a required keyword parameter and then call it using the keyword syntax.
def does_keyword(first, second, *, third): '''Prints parameters.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') does_keyword(1, 2, third=3) First: 1 Second: 2 Third: 3
If you try to call a required keyword parameter using positional syntax, you get an error:
does_keyword(1, 2, 3) ------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-88b97f8a6c32> in <module> ----> 1 does_keyword(1, 2, 3) TypeError: does_keyword() takes 2 positional arguments but 3 were given
You can make a parameter optional by assigning to it a default value in the function definition. This value will be used if no value is provided for the parameter during the function call. Listing 6.5 defines a function, does_defaults()
, whose third parameter has the default value 3
. The function is then called twice: first using positional assignment for all three parameters and then using the default value for the third.
def does_defaults(first, second, third=3): '''Prints parameters.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') does_defaults(1, 2, 3) First: 1 Second: 2 Third: 3 does_defaults(1, 2) First: 1 Second: 2 Third: 3
Much as with the restriction regarding the order of keyword and position arguments during a function call, you cannot define a function with a default value parameter to the left of a non-default value parameter:
def does_defaults(first=1, second, third=3): '''Prints parameters.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') File "<ipython-input-19-a015eaeb01be>", line 1 def does_defaults(first=1, second, third=3): ^ SyntaxError: non-default argument follows default argument
Default values are defined in the function definition, not in the function call. This means that if you use a mutable object, such as a list or dictionary, as a default value, it will be created once for the function. Every time you call that function using that default, the same list or dictionary object will be used. This can lead to subtle problems if it is not expected. Listing 6.6 defines a function with a list as the default argument. The code block appends 1
to the list. Notice that every time the function is called, the list retains the values from previous calls.
def does_list_default(my_list=[]): '''Uses list as default.''' my_list.append(1) print(my_list) does_list_default() [1] does_list_default() [1, 1] does_list_default() [1, 1, 1]
Generally, it’s a good practice to avoid using mutable objects as default parameters to avoid difficult-to-trace bugs and confusion. Listing 6.7 demonstrates a common pattern to handle default values for mutable parameter types. The default value in the function definition is set to None
. The code block tests whether the parameter has an assigned value. If it does not, a new list is created and assigned to the variable. Because the list is created in the code block, a new list is created every time the function is called without a value supplied for the parameter.
def does_list_param(my_list=None): '''Assigns default in code to avoid confusion.''' my_list = my_list or [] my_list.append(1) print(my_list) does_list_param() [1] does_list_param() [1] does_list_param() [1]
As of Python 3.8, you can restrict parameters to positional assignment only. A parameter to the left of a forward slash (/
) in a function definition is restricted to positional assignment. Listing 6.8 defines the function does_positional
so that its first parameter, first
, is positional only.
def does_positional(first, /, second, third): '''Demonstrates a positional parameter.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') does_positional(1, 2, 3) First: 1 Second: 2 Third: 3
If you try to call does_positional
by using keyword assignment for first
, you get an error:
does_positional(first=1, second=2, third=3) ----------------------------------------------------------------------- TypeError Traceback (most recent call las t) <ipython-input-24-7b1f45f64358> in <module> ----> 1 does_positional(first=1, second=2, third=3) TypeError: does_positional() got some positional-only arguments passed as keyword arguments: 'first'
Listing 6.9 modifies does_positional
to use positional-only and keyword-only parameters. The parameter first
is positional only, the parameter second
can be set using positional or keyword assignment, and the last, third
, is keyword only.
def does_positional(first, /, second, *, third): '''Demonstrates a positional and keyword parameters.''' print(f'First: {first}') print(f'Second: {second}') print(f'Third: {third}') does_positional(1, 2, third=3) First: 1 Second: 2 Third: 3
You can use wildcards in function definitions to accept an undefined number of positional or keyword arguments. This is often done when a function calls a function from an outside API. The function can pass the arguments through without requiring that all of the outside API’s parameters be defined.
To use a wildcard for positional parameters, you use the *
character. Listing 6.10 demonstrates the definition of a function with the positional wildcard parameter *args
. The code block receives any positional arguments given in a function call as items in a list named args
. This function goes through the list and prints each item. The function is then called with the arguments 'Donkey'
, 3
, and ['a']
, each of which is accessed from the list and printed.
def does_wildcard_positions(*args): '''Demonstrates wildcard for positional parameters.''' for item in args: print(item) does_wildcard_positions('Donkey', 3, ['a']) Donkey 3 ['a']
To define a function with keyword wildcard parameters, you define a parameter that starts with **
. For example, Listing 6.11 defines the function does_wildcard_keywords
with the parameter **kwargs
. In the code block, the keyword parameters are available as keys and values in the dictionary kwargs
.
def does_wildcard_keywords(**kwargs): '''Demonstrates wildcard for keyword parameters.''' for key, value in kwargs.items(): print(f'{key} : {value}') does_wildcard_keywords(one=1, name='Martha') one : 1 name : Martha
You can use both positional and keyword wildcard parameters in the same function: Just define the positional parameters first and the keyword parameters second. Listing 6.12 demonstrates a function using both positional and keyword parameters.
def does_wildcards(*args, **kwargs): '''Demonstrates wildcard parameters.''' print(f'Positional: {args}') print(f'Keyword: {kwargs}') does_wildcards(1, 2, a='a', b=3) Positional: (1, 2) Keyword: {'a': 'a', 'b': 3}
Return statements define what value a function evaluates to when called. A return statement consists of the keyword return
followed by an expression. The expression can be a simple value, a more complicated calculation, or a call to another function. Listing 6.13 defines a function that takes a number as an argument and returns that number plus 1.
def adds_one(some_number): '''Demonstrates return statement.''' return some_number + 1 adds_one(1) 2
Every Python function has a return value. If you do not define a return statement explicitly, the function returns the special value None
:
def returns_none(): '''Demonstrates default return value.''' pass returns_none() == None True
This example omits a return statement and then tests that the value returned is equal to None
.
Scope refers to the availability of objects defined in code. A variable defined in the global scope is available throughout your code, whereas a variable defined in a local scope is available only in that scope. Listing 6.14 defines a variable outer
and a variable inner
. Both variables are available in the code block of the function shows_scope
, where you print them both.
outer = 'Global scope' def shows_scope(): '''Demonstrates local variable.''' inner = 'Local scope' print(outer) print(inner) shows_scope() Global scope Local scope
The variable inner
is local to the function, as it is defined in the function’s code block. If you try to call inner
from outside the function, it is not defined:
print(inner) ------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-39-9504624e1153> in <module> ----> 1 print(inner) NameError: name 'inner' is not defined
Understanding scope is useful when you use decorators, as described in the next section.
A decorator enables you to design functions that modify other functions. Decorators are commonly used to set up logging using a set convention or by third-party libraries. While you may not need to write your own decorators, it is useful to understand how they work. This section walks through the concepts involved.
In Python, everything is an object, including functions. This means you can point a variable to a function. Listing 6.15 defines the function add_one(n)
, which takes a number and adds 1
to it. Next, it creates the variable my_func
, which has the function add_one()
as its value.
Note
When you are not calling a function, you do not use parentheses in the variable assignment. By omitting the parentheses, you are referring to the function object and not to a return value. You can see this where Listing 6.15 prints my_func
, which is indeed a function object. You can then call the function by adding the parentheses and argument to my_func
, which returns the argument plus 1
.
def add_one(n): '''Adds one to a number.''' return n + 1 my_func = add_one print(my_func) <function add_one at 0x1075953a0> my_func(2) 3
Because functions are objects, you can use them with data structures such as dictionaries or lists. Listing 6.16 defines two functions and puts them in a list pointed to by the variable my_functions
. It then iterates through the list, assigning each function to the variable my_func
during its iteration and calling the function during the for
loop’s code block.
def add_one(n): '''Adds one to a number.''' return n + 1 def add_two(n): '''Adds two to a number.''' return n + 2 my_functions = [add_one, add_two] for my_func in my_functions: print(my_func(1)) 2 3
Python allows you to define a function as part of another function’s code block. A function defined in this way is called a nested function. Listing 6.17 defines the function nested()
in the code block of the function called_nested()
. This nested function is then used as a return value for the outer function.
def call_nested(): '''Calls a nested function.''' print('outer') def nested(): '''Prints a message.''' print('nested') return nested my_func = call_nested() outer my_func() nested
You can also wrap one function with another, adding functionality before or after. Listing 6.18 wraps the function add_one(number)
with the function wrapper(number)
. The wrapping function takes a parameter, number
, which it then passes to the wrapped function. It also has statements before and after calling add_one(number)
. You can see the order of the print statements when you call wrapper(1)
and see that it returns the expected values from add_one
: 1
and 2
.
def add_one(number): '''Adds to a number.''' print('Adding 1') return number + 1 def wrapper(number): '''Wraps another function.''' print('Before calling function') retval = add_one(number) print('After calling function') return retval wrapper(1) Before calling function Adding 1 After calling function 2
It is also possible to go a step further and use a function as a parameter. You can pass a function as a value to a function that has a nested function definition wrapping the function that was passed. For example, Listing 6.19 first defines the function add_one(number)
as before. But now it defines the function wrapper(number)
nested in the code block of a new function, do_wrapping(some_func)
. This new function takes a function as an argument and then uses that function in the definition of wrapper(number)
. It then returns the newly defined version of wrapper(number)
. By assigning this result to a variable and calling it, you can see the wrapped results.
def add_one(number): '''Adds to a number.''' print('Adding 1') return number + 1 def do_wrapping(some_func): '''Returns a wrapped function.''' print('wrapping function') def wrapper(number): '''Wraps another function.''' print('Before calling function') retval = some_func(number) print('After calling function') return retval return wrapper my_func = do_wrapping(add_one) wrapping function my_func(1) Before calling function Adding 1 After calling function 2
You can use do_wrapping(some_func)
to wrap any function that you like. For example, if you have the function add_two(number)
, you can pass it as an argument just as you did add_one(number)
:
my_func = do_wrapping(add_two) my_func(1) wrapping function Before calling function Adding 2 After calling function 3
Decorators provide syntax that can simplify this type of function wrapping. Instead of calling do_wrapping(some_func)
, assigning it to a variable, and then invoking the function from the variable, you can simply put @do_wrapping
at the top of the function definition. Then the function add_one(number)
can be called directly, and the wrapping happens behind the scenes.
You can see in Listing 6.20 that add_one(number)
is wrapped in a similar fashion as in Listing 6.18, but with the simpler decorator syntax.
def do_wrapping(some_func): '''Returns a wrapped function.''' print('wrapping function') def wrapper(number): '''Wraps another function.''' print('Before calling function') retval = some_func(number) print('After calling function') return retval return wrapper @do_wrapping def add_one(number): '''Adds to a number.''' print('Adding 1') return number + 1 wrapping function add_one(1) Before calling function Adding 1 After calling function 2
The vast majority of the time you define functions, you will want to use the syntax for named functions. This is what you have seen up to this point. There is an alternative, however: the unnamed, anonymous function. In Python, anonymous functions are known as lambda functions, and they have the following syntax:
lambda <Parameter>: <Statement>
where lambda
is the keyword designating a lambda function, <Parameter>
is an input parameter, and <Statement>
is the statement to execute using the parameter. The result of <Statement>
is the return value. This is how you define a lambda function that adds one to an input value:
lambda x: x +1
In general, your code will be easier to read, use, and debug if you avoid lambda functions, but one useful place for them is when a simple function is applied as an argument to another. Listing 6.21 defines the function apply_to_list(data, my_func)
, which takes a list and a function as arguments. When you call this function with the intention of adding 1 to each member of the list, the lambda function is an elegant solution.
def apply_to_list(data, my_func): '''Applies a function to items in a list.''' for item in data: print(f'{my_func(item)}') apply_to_list([1, 2, 3], lambda x: x + 1) 2 3 4
Functions, which are important building blocks in constructing complex programs, are reusable named blocks of code. Functions are documented with docstrings. Functions can accept parameters in a number of ways. A function uses a return statement to pass a value at the end of its execution. Decorators are special functions that wrap other functions. Anonymous, or lambda, functions are unnamed.
For Questions 1–3, refer to Listing 6.22.
def add_prefix(word, prefix='before-'): '''Prepend a word.''' return f'{prefix}{word}'3 def return_one(): return 1 def wrapper(): print('a') retval = return_one() print('b') print(retval)
1. What would be the output of the following call:
add_prefix('nighttime', 'after-')
2. What would be the output of the following call:
add_prefix('nighttime')
3. What would be the output of the following call:
add_prefix()
4. Which line should you put above a function definition to decorate it with the function standard_logging ?
*standard_logging **standard_logging @standard_logging [standard_logging]
5. What would be printed by the following call:
wrapper()
3.22.68.49