Python functions are either named or anonymous set of statements or expressions. In python, functions are first class objects. This means that there is no restriction on the usage of functions. Python functions can be used just like any other python value such as strings and numbers. Python functions have attributes that can be introspected on using the inbuilt python dir
function as shown below:
def square(x):
return x**2
>>> square
<function square at 0x031AA230>
>>> dir(square)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>>
Some important function attributes include:
__doc__
returns the documentation string for the given function.
def square(x):
"""return square of given number"""
return x**2
>>> square.__doc__
'return square of given number'
__name__
returns function name.
def square(x):
"""return square of given number"""
return x**2
>>> square.func_name
'square'
__module__
returns the name of module function is defined in.
def square(x):
"""return square of given number"""
return x**2
>>> square.__module__
'__main__'
func_defaults
returns a tuple of the default argument values. Default arguments are discussed later on.func_globals
returns a reference to the dictionary that holds the function’s global variables.
def square(x):
"""return square of given number"""
return x**2
>>> square.func_globals
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'square': <function square at 0x10f099c08>, '__doc__': None, '__package__': None}
func_dict
returns the namespace supporting arbitrary function attributes.
def square(x):
"""return square of given number"""
return x**2
>>> square.func_dict
{}
func_closure
returns tuple of cells that contain bindings for the function’s free variables. Closures are discussed later on.
Functions can be passed around as arguments to other functions. These functions that take other functions as argument are commonly referred to as higher order functions and these form a very important part of functional programming. A very good example of this higher order functions is the map
function that takes a function and an iterable
and applies the function to each item in the iterable
returning a new list. In the example below we illustrate this by passing the square
function previously defined and an iterable
of numbers to the map
function.
>>> map(square, range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Functions can also be defined inside other function code blocks and can be returned from other function calls.
def outer():
outer_var = "outer variable"
def inner():
return outer_var
return inner
In the above example, we define a function, inner
within another function, outer
and return the inner
function when the outer
function is executed. Functions can also be assigned to variables just like any other python object as shown below:
def outer():
outer_var = "outer variable"
def inner():
return outer_var
return inner
>>> func = outer()
>>> func
<function inner at 0x031AA270>
>>>
In the above example, the outer
function returns a function when called and this is assigned to the variable func
. This variable can be called just like the returned function:
>>> func()
'outer variable'
Function Definitions
The def
keyword is used to create user-defined functions. Functions definitions are executable statements.
def square(x):
return x**2
In the square
function above, when the module containing the function is loaded into python interpreter or if it is defined within the python REPL then the function definition statement which is def square(x)
is executed. This has some implications for default arguments that have mutable data structure as values; this will covered later on in this tutorial. The execution of a function definition binds the function name in the current local namespace (think of namespaces as name to value mappings that can also be nested. namespaces and scopes are covered in more detail in another tutorial) to a function object which is a wrapper around the executable code for the function. This function object contains a reference to the current global namespace which is the global namespace that is used when the function is called. The function definition does not execute the function body; this gets executed only when the function is called.
Function Call Arguments
In addition to normal arguments, python functions support variable number of arguments. These variable number of arguments come in three flavors that are described below:
- Default Argument Values: This allows a user to define some default
values for function arguments. In this case, such a function can be called
with fewer arguments. Python uses the default supplied values for
arguments that are not supplied during function call. This example below is illustrative:def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2)
The above function has been defined with a single normal positional
argument,arg
and two default arguments,def_arg
anddef_arg2
. The above function can be called in any of the following ways below:- Supplying non-default positional argument values only; in this case the other arguments take on the supplied default values:
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility") 'arg=tranquility, def_arg=1, def_arg2=2'
- Supplying values to override some default arguments in addition to the non-default positional arguments:
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility", "to Houston") 'arg=tranquility, def_arg=to Houston, def_arg2=2'
- Supplying values for all arguments overriding even arguments with default values.
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility", "to Houston", "the eagle has landed") 'arg=tranquility, def_arg=to Houston, def_arg2=the eagle has landed'
It is also very important to be careful when using mutable default data structures as default arguments. Function definitions get executed only once so these mutable data structures which are reference values are created once at definition time. This means that the same mutable data structure is used for all function calls as shown below:
def show_args_using_mutable_defaults(arg, def_arg=[]): def_arg.append("Hello World") return "arg={}, def_arg={}".format(arg, def_arg) >>> show_args_using_mutable_defaults("test") "arg=test, def_arg=['Hello World']" >>> show_args_using_mutable_defaults("test 2") "arg=test 2, def_arg=['Hello World', 'Hello World']"
On every function call,
Hello World
is added to thedef_arg
list and after two function calls the default argument has two hello world strings. It is important to take note of this when using mutable default arguments as default values. The reason for this will become clearer when we discuss the python data model.
- Supplying non-default positional argument values only; in this case the other arguments take on the supplied default values:
- Keyword Argument: Functions can be called using keyword
arguments of the formkwarg=value
. Akwarg
refers to the name of arguments used in a function definition. Take the function defined below with positional non-default and default arguments.def show_args(arg, def_arg=1): return "arg={}, def_arg={}".format(arg, def_arg)
To illustrate function calls with key word arguments, the following function can be called in any of the following ways:
show_args(arg="test", def_arg=3)
show_args(test)
show_args(arg="test")
show_args("test", 3)
In a function call, keyword arguments must not come before non-keyword arguments thus the following will fail:
show_args(def_arg=4)
A function cannot supply duplicate values for an argument so the following is illegal:
show_args("test", arg="testing")
In the above the argument
arg
is a positional argument so the value
test
is assigned to it. Trying to assign to the keywordarg
again
is an attempt at multiple assignment and this is illegal.All the keyword arguments passed must match one of the arguments accepted by the function and the order of keywords including non-optional arguments is not important so the following in which the order of argument is switched is legal:
show_args(def_arg="testing", arg="test")
- Arbitrary Argument List: Python also supports defining functions that take arbitrary number of arguments that are passed to the function in a
tuple. An example of this from the python tutorial is given below:def write_multiple_items(file, separator, *args): file.write(separator.join(args))
The arbitrary number of arguments must come after normal arguments; in this case, after the
file
andseparator
arguments. The following is an example of function calls to the above defined function:f = open("test.txt", "wb") write_multiple_items(f, " ", "one", "two", "three", "four", "five")
The arguments
one two three four five
are all bunched together into a tuple that can be accessed via theargs
argument.
Unpacking Function Argument
Sometimes, we may have arguments for a function call either in a tuple, a list or a dict. These arguments can be unpacked into functions for function calls using *
or **
operators. Consider the following function that takes two positional arguments and prints out the values
def print_args(a, b):
print a
print b
If we had the values we wanted to supply to the function in a list then we could unpack these values directly into the function as shown below:
>>> args = [1, 2]
>>> print_args(*args)
1
2
Similarly, when we have keywords, we can use dicts
to store kwarg to value
mapping and the **
operator to unpack the keyword arguments to the functions as shown below:
>>> def parrot(voltage, state=’a stiff’, action=’voom’):
print "-- This parrot wouldn’t", action,
print "if you put", voltage, "volts through it.",
print "E’s", state, "!"
>>> d = {"voltage": "four million", "state": "bleedin’ demised", "action": "VOOM"}
>>> parrot(**d)
>>> This parrot wouldn’t VOOM if you put four million volts through it. E’s bleedin’ demised
Defining Functions with *
and **
Sometimes, when defining a function, we may not know before hand the number of arguments to expect. This leads to function definitions of the following signature:
show_args(arg, *args, **kwargs)
The *args
argument represents an unknown length of sequence of positional arguments while **kwargs
represents a dict of keyword name value mappings which may contain any amount of keyword name value mapping. The *args
must come before **kwargs
in the function definition. The following illustrates this:
def show_args(arg, *args, **kwargs):
print arg
for item in args:
print args
for key, value in kwargs:
print key, value
>>> args = [1, 2, 3, 4]
>>> kwargs = dict(name='testing', age=24, year=2014)
>>> show_args("hey", *args, **kwargs)
hey
1
2
3
4
age 24
name testing
year 2014
The normal argument must be supplied to the function but the *args
and **kwargs
are optional as shown below:
>>> show_args("hey", *args, **kwargs)
hey
At function call the normal argument is supplied normally while the optional arguments are unpacked into the function call.
Anonymous Functions
Python also has support for anonymous functions. These functions are created using the lambda
keyword. Lambda expressions in python are of the form:
lambda_expr ::= "lambda" [parameter_list]: expression
Lambda expressions return function objects after evaluation and have same attributes as named functions. Lambda expressions are normally only used for very simple functions in python as shown below.
>>> square = lambda x: x**2
>>> for i in range(10):
square(i)
0
1
4
9
16
25
36
49
64
81
>>>
The above lambda expression is equivalent to the following named function:
def square(x):
return x**2
Nested functions and Closures
Function definitions within a function creates nested functions just as shown below:
```python
def outer():
outer_var = "outer variable"
def inner():
return outer_var
return inner
```
In this type of function definition, the function inner
is only in scope inside the function outer
, so it is most often useful when the inner function is being returned (moving it to the outer scope) or when it is being passed into another function. In nested functions such as in the above, a new instance of the nested function is created on each call to outer function. That is because during each execution of the outer function the definition of the new inner function is executed but the body is not executed.
A nested function has access to the environment in which it was created. This is a direct result of the semantics of python function definition. A result is that a variable defined in the outer function can be referenced in the inner function even after the outer functions has finished execution.
def outer():
outer_var = "outer variable"
def inner():
return outer_var
return inner
>>> x = outer()
>>> x
<function inner at 0x0273BCF0>
>>> x()
'outer variable'
When nested functions reference variables from outer functions we say the nested function is closed over the referenced variable. We can use one of the special attribute of function objects, __closure__
to access the closed variables as shown below:
>>> cl = x.__closure__
>>> cl
(<cell at 0x029E4470: str object at 0x02A0FD90>,)
>>> cl[0].cell_contents
'outer variable'
Closures in python have a quirky behavior. In python 2.x and below, variables that point to immutable types such as string and numbers cannot be rebound within a closure. The example below illustrates this
def counter():
count = 0
def c():
count += 1
return count
return c
>>> c = counter()
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
UnboundLocalError: local variable 'count' referenced before assignment
A rather wonky solution to this is to make use of a mutable type to capture the closure as shown below:
def counter():
count = [0]
def c():
count[0] += 1
return count[0]
return c
>>> c = counter()
>>> c()
1
>>> c()
2
>>> c()
3
Python 3 introduces the nonlocal
key word that can be used to fix this closure scoping issue as shown below. In the tutorial on namespaces, we describe these quirks in more details.
def counter():
count = 0
def c():
nonlocal count
count += 1
return count
return c
Closures can be used for maintaining states (isn’t that what classes are for) and for some simple cases provide a more succinct and readable solution than classes. We use a logging example curled from tech_pro to illustrate this. Imagine an extremely trivial logging API using class-based object orientation that can log at different levels:
class Log:
def __init__(self, level):
self._level = level
def __call__(self, message):
print("{}: {}".format(self._level, message))
log_info = Log("info")
log_warning = Log("warning")
log_error = Log("error")
This same functionality can be implemented with closures as shown below:
def make_log(level):
def _(message):
print("{}: {}".format(level, message))
return _
log_info = make_log("info")
log_warning = make_log("warning")
log_error = make_log("error")
Leave a Reply