A friend of mine is learning Python and was curious about functional programming, so I have written this brief tutorial. Python isn’t a full-fledged functional language, but it supports some very useful functional idioms.

It’s best to approach this tutorial by programming along at the Python interactive prompt. Try typing everything in to see what the results are.

### Introduction

Imagine you have a list of numbers and want to filter out all even numbers:

q = [1,2,5,6,8,12,15,17,20,23,24] def is_even(x) : return x % 2 == 0 result = filter(is_even, q)

(Remember that the “%” operator is the modulus or remainder operator. If the remainder when a number is divided by two is zero, then the number must be even.)

If we only use `is_even` once, it’s kind of annoying that we have to define it. Wouldn’t it be nice if we could just define it inside of the call to `filter`? We want to do something like “`q = filter(x % 2 == 0, q)`“, but that won’t quite work, since the “`x % 2 == 0`” is evaluated before the call to `filter`. We want to pass a function to filter, not an expression.

We can do this using the `lambda` operator. `lambda` allows you to create an unnamed throw-away function. Try this:

q = [1,2,5,6,8,12,15,17,20,23,24] result = filter(lambda x : x % 2 == 0, q)

`lambda` tells Python that a function follows. Before the colon (:) you list the parameters of the function (in this case, only one, *x*). After the colon you list what the function returns. This function doesn’t have a name, and Python disposes of it as soon as `filter` is done with it.

You can, however, assign the function to a variable to give it a name. The following two statements are identical:

def is_odd(x) : return x % 2 == 1 is_odd = lambda x : x % 2 == 1

### map

Here’s another example. The `map` function applys a function to every item in a list, and returns the result. This example adds 1 to every element in the list:

result = map(lambda x : x + 1, [1,2,3,4,5])

(At this point, `result` holds `[2,3,4,5,6]`.)

The `map` function can also process more than one list at a time, provided they are the same length. This allows you to do things like add all of the elements in a pair of lists. Note that our lambda here has two parameters:

result = map(lambda x,y : x+y, [1,2,3,4,5], [6,7,8,9,10])

(At this point, `result` holds `[7, 9, 11, 13, 15]`.)

### reduce

Python also provides the `reduce` function. This is a bit more complicated; you provide it a list and a function, and it reduces that list by applying the function to pairs of elements in the list. An example is the best way to understand this. Let’s say we want to find the sum of all the elements in a list:

sum = reduce(lambda x,y : x+y, [1,2,3,4,5])

`reduce` will first apply the function to 1,2, yielding 3. It will then apply the function to 3,3 (the first 3 is the result of adding 1+2), yielding 6. It will then apply the function to 6,4 (the 6 is the result of adding 3+3), yielding 10. Finally, it will apply the function to 10,5, yielding 15. The result stored in `sum` is 15.

Similarly, we can find the cumulative product of all items in a list. The following example stores 120 (=1*2*3*4*5) in `product`:

product = reduce(lambda x,y : x*y, [1,2,3,4,5])

### Conditionals

Beginning in Python 2.5 you can even express conditions within your lambdas, using Python’s conditional expressions. So, for example:

reciprocals = map(lambda x : 1.0/x if x != 0 else None, [0, 1, 2, 3, 4, 5])

At this point, `reciprocals` holds the list `[None, 1.0, 0.5, 0.333..., 0.25, 0.2]`. The expression “1/x if x != 0 else None” is the conditional expression, and it is used to prevent division by zero. If *x* is not 0, then the result is 1/x, but otherwise it is None.

### Background

One of the advantages of lambda-functions is that they allow you to write very concise code. A result of this, however, is that the code is dense with meaning, and it can be hard to read if you are not accustomed to functional programming. In our first example, `filter(is_even, q)` was fairly easy to understand (fortunately, we chose a descriptive function name), while `filter(lambda x : x % 2 == 0, q)` takes a little longer to comprehend.

If you are a masochistic mathematical geek, you’ll probably enjoy this (I do). If not, you might still prefer to break things into smaller pieces by defining all of your functions first and then using them by name. That’s ok! Functional programming isn’t for everyone.

Another advantage of functional programming is that it corresponds very closely to the mathematical notion of functions. Note that our lambda-functions didn’t store any results into variables, or have any other sort of side effect. Functions without side effects cause fewer bugs, because their behavior is deterministic (this is related to the dictum that you aren’t supposed to use global variables). It is also much easier to mathematically prove that such functions do what you think they are doing. (Note that it’s still possible to write a lambda function that has side effects, if the lambda function calls another function that has side effects; for example `lambda x : sys.stdout.write(str(x))` has the side effect of printing its parameter to the screen.)

“you might still prefer to break things into smaller pieces by defining all of your functions first and then using them by name. That’s ok! Functional programming isn’t for everyone.”

Functional programming is not the same as deeply nested maps, filters, and reduces. Variable definitions are perfectly OK in a functional programming style, and breaking things into really tiny bits is considered very good FP style nowadays.

LikeLike

Ironically, because you decided to define is_odd and is_even as lambda expressions, you missed out on another major pillar of FP, function composition.

You could have shown:

is_even = lambda x: x % 2 == 0

is_odd = lambda x: not(is_even(x))

And, as far as things go, most people find list comprehensions easier to deal with, e.g.

[x for x in range(10) if is_even(x)] # equivalent to filter(is_even, range(10))

[x + 1 for x in range(10)] # equivalent to map(lambda x: x + 1, range(10))

[x + 1 for x in range(10) if is_even(x)] # equivalent to map(lambda x: x + 1, filter(is_even, range(10)))

Those three examples show how much easier list comprehensions are to read compared with their map and filter cousins. Not to mention that lambdas end up being slower! [1]

[1] http://www.artima.com/weblogs/viewpost.jsp?thread=98196

LikeLike

Thanks for the comments and improvements — my original perspective was a bit naive!

LikeLike