The Function

from intermediate pythonista

Jan 18, 2015, 3:13:04 PM

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 and def_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 the def_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.

  • Keyword Argument: Functions can be called using keyword
    arguments of the form kwarg=value. A kwarg 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 keyword arg 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 and separator 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 the args 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")

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