Function objects and lambdas

One of the first things you learn to do in Python is to call functions, for example you might have used print and input functions to create a simple “What is your name” type program. You will also have learned how to create your own functions using the def operator.

There is one more important thing to know about functions in Python - a function is an object, just like an integer, a string or a list. This means, for example:

Functions are first class objects in Python - this simply means that function objects are just like any other objects, with no special restrictions on their use. This feature allows Python to support the functional programming paradigm.

This tutorial doesn’t cover functional programming id depth, it mainly looks some practical uses of function objects. We will also meet lambda functions aka anonymous functions.

All functions are objects

This code defines a simple function and then prints its type

def square(x):
    return x*x

print(type(square))

The result is

<class 'function'>

This tells us that the object square is of type function. Notice that we take the type of square, the variable that holds the object, rather than square() (that would try to call the function).

Since square holds an object, we can assign it to another variable:

sq = square

Now sq and square both hold the same function object.

Applying a function

So now that we have a variable sq that holds a function object, how do we call the function? This is often called applying the function. Well is it actually vary easy. You just use the normal function calling syntax:

x = sq(2)

The name sq is the variable that holds the function pointer. When we use the () operator, it applies the function to the value passed in (ie it calls the function with the value 2).

We sometimes say that sq is an alias of square, but all this really means is that the two variables both refer to the same function object.

Passing a function as a parameter

Here is some sample code that takes a list of names and prints them out. The names are stored as tuples containing the first name and last name. The function print_names prints the names out, one per line.

However, we would like to be able to control how the names are formatted. So we make our print_names function accept a formatter function object. The formater function accepts a name tuple and returns a string.

As an example, the format_space function joins the first and last names, with a space between them:

names = [ ('John', 'Smith'),
          ('Anne', 'Jones'),
          ('Robert', 'Davis')]

def print_names(names, formatter):
    for name in names:
        print(formatter(name))

def format_space(name):
    return ' '.join(name)

print_names(names, format_space)

As you might expect, this create the following output:

John Smith
Anne Jones
Robert Davis

Now suppose we wanted to print a list of the names in a different format, say surname plus initial. We can do this without changing the print_names function, simply by writing a new formatter function and passing that into the call:

def format_initial(name):
    return name[1] + ', ' + name[0][0]

print_names(names, format_initial)

This prints the second part of the name, plus a comma, plus the first character of the first name, giving this output:

Smith, J
Jones, A
Davis, R

Finally, what if we wanted to print out the email addresses - formed from the lower case names, separated by a dot, followed by the domain name. Like this:

def format_email(name):
    return '.'.join(name).lower() + '@example.com'

print_names(names, format_email)

This gives:

john.smith@example.com
anne.jones@example.com
robert.davis@example.com

Higher order functions

A higher order function if a function that operates on functions - it either accepts function objects as parameter, or returns a function object, or both.

The map() function

One example of a higher order function, consider the built-in map function. This takes a function object and applies it to a sequence of inputs. Here is an example using the square function:

def square(x):
    return x*x

numbers = [1, 3, 5, 7]

result = map(square, numbers)
print(list(result))

The map function applies the square function to each element in numbers, and returns the result. Note that the result is a map object, which is a lazy operator. You can iterate over a map object, for example in a for loop. But if you want to print the result out as a list of values, youmust first convert the lazy sequence into an actual list (using the list function). This gives the result:

[1, 9, 25, 49]

(the square of each input value).

Functions that return functions

This is where it gets very powerful. A higher order function can transform one function into another, related function. For example:

def twice(f):
    def g(x):
        return f(f(x))
    return g

The function twice accepts a parameter f, which should take a single parameter.

Inside the function twice, we define a nested function g that calculates f of f of x - that is, it applies f twice.

But here is the clever bit. twice returns the function g, that is a brand new function that apples f twice, whatever f might be!

So if we have an increment function that adds 1 to x, we can create an inc2 function that adds 1 to x then adds 1 to the result (that is, it adds 2 to x). This code prints 7:

def increment(x):
    return x + 1

inc2 = twice(increment)
print(inc2(5))

Similar, if we have a square function, we can create an sq2 function that squares x then squares the result (that is, it raises x to the power 4). This prints 81:

def square(x):
    return x*x

sq2 = twice(square)
print(sq2(3))

Lambda functions

In the map example above, we have defined a function square simply so that we can use it in the call to the map function.

def square(x):
    return x*x

numbers = [1, 3, 5, 7]

result = map(square, numbers)
print(list(result))

Despite sounding quite exotic, a lambda function is simply a neat bit of syntax you can use to create small simple functions in line, without the hassle of creating a full function definition, and without needing to bother giving it a name (it is an anonymous function). Here is how it is used:

numbers = [1, 3, 5, 7]

result = map(lambda x: x*x, numbers)
print(list(result))

Here, the fragment:

lambda x: x*x

creates a function object that takes a parameter x and returns the value x squared. The function object isn’t given a name because it is used in place, passed directly into the map call.

In the case of the twice function, lambdas can really simplify things. Here is the original code:

def twice(f):
    def g(x):
        return f(f(x))
    return g

We can actually replace the inner function g with a lambda (the name g isn’t used outside the function anyway), giving us this:

def twice(f):
    return lambda x: f(f(x))

Not inly is this shorter, it really is a clearer expression of what is actually going on. twice returns a function that has the effect of applying f twice.