The Function

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:

def square(x):
    """return square of given number"""
    return x**2

>>> square.__doc__
'return square of given number'
def square(x):
    """return square of given number"""
    return x**2

>>> square.func_name
'square'
def square(x):
    """return square of given number"""
    return x**2

>>> square.__module__
'__main__'
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}
def square(x):
    """return square of given number"""
    return x**2

>>> square.func_dict
{}

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:

 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")

The closure based version as can be seen is way more succinct and readable even though both versions implement exactly the same functionality. Closures also play a major role in a major python function, function decorators. This is a very widely used functionality that is explained in the next tutorial.

Did you spot any error, issue or have you got a topic you would like me to write about? Contact me on twitter using my handle @obi_inc

 Further Reading

Closures in Python

Defining Functions

 
455
Kudos
 
455
Kudos

Now read this

Intermezzo: A little History

The Evolution of Python In December 1989, Guido Van Rossum started work on a language that he christened Python. Guido Van Rossum had been part of the team that worked on the ABC programming language as part of the Amoeba operating... Continue →