We’ve seen how to define a function using the def keyword. This is the standard method of function definition in any case where more than a single expression is to be run. Occasionally, we wish to run a single expression as a function, and for this Python provides the *anonymous* or *lambda* function.

## Defining lambda functions

For example, suppose we want a function to calculate the hypotenuse of a right-angled triangle, given the other two sides. We could use the standard function definition to do this, as in

from math import * def hypot(a, b): return sqrt(a*a + b*b) print(hypot(3, 4))

The output is 5.0, as we would hope. We can do the same thing using a lambda function, as in:

from math import * hypot = lambda a, b: sqrt(a*a + b*b) print(hypot(3, 4))

On line 2, we used the keyword `lambda`

to define an anonymous function, which is then assigned to the variable `hypot`

, which becomes the function’s name. We can then call this function in the usual way.

The general syntax for a lambda function is

lambda <arguments>: <expression>

Here, `lambda`

is a keyword indicating that an anonymous function is being defined; it’s *not* the name of the function. At this stage, the function itself has no name (hence ‘anonymous’). Note that, unlike a function definition using `def`

, the list of arguments is not enclosed in parentheses. The end of the argument list is denoted with the colon.

Following the colon, we must give one (and only one) expression which provides a value that the function returns. The expression must be just that, an expression, and not a statement that doesn’t provide a value to return. For example, we cannot say

lambda x, a: x = a

An assignment statement in Python does not return a value (unlike some other languages), so is not valid as an expression in a lambda function.

At this point, lambda functions may seem rather pointless. In the hypotenuse example above, we couldn’t use the function without assigning it to a variable (`hypot`

) and thus giving it a name which we could then use to call the function. If the only way we can use an anonymous function is to give it a name, then what’s its use? In fact, there are ways of using anonymous functions without naming them.

## Multiway branches

Here’s one example of using lambda functions without naming them. The formulas for the areas of regular polygons with side and the circle with radius are

triangle | |

square | |

pentagon | |

hexagon | |

septagon | |

octagon | |

nonagon | |

decagon | |

circle |

The following program allows users to enter the name of the shape and its edge length to calculate its area:

from math import * area = {'triangle': lambda x: sqrt(3) * x * x / 4, 'square': lambda x: x * x, 'pentagon': lambda x: sqrt(5 * (5 + 2*sqrt(5))) * x * x / 4, 'hexagon': lambda x: 3 * sqrt(3) * x * x / 2, 'septagon': lambda x: 7 * x * x * cos(pi/7) / sin(pi/7) / 4, 'octagon': lambda x: 2 * (1 + sqrt(2)) * x * x, 'nonagon': lambda x: 9 * x * x * cos(pi/9) / sin(pi/9) / 4, 'decagon': lambda x: 5 * x * x * sqrt(5 + 2 * sqrt(5)) / 2, 'circle': lambda x: pi * x * x } while True: figure = input('Enter figure and side length (or "quit"):') if figure == 'quit': break figure = figure.split() print('Area = ', area[figure[0]](float(figure[1])))

The `area`

object is a dictionary whose keys are the names of the shapes and whose values are lambda functions for calculating the areas. The user can input a line such as `pentagon 12`

. This is split into a list with two elements on line 17. `figure[0]`

contains ‘pentagon’ which is the key passed to the dictionary on line 18. The object `area[figure[0]]`

is the value of `area['pentagon']`

, which is the lambda function for calculating the pentagon’s area. The parenthses `(float(figure[1]))`

after `area[figure[0]]`

passes the argument `float(figure[1])`

to this function, which then returns the area of the pentagon. (The cotangent function isn’t available in the math library, so I’ve used the trig formula .)

The important point here is that a Python function is an object that can be passed around a program just like any other object. In this case, the values of the `area`

dictionary are all functions, and thus querying this dictionary returns a function which can then be called by providing its arguments in parentheses. Note that we did not need to give any of the lambda functions names in order to call them.

It’s appropriate to use lambda functions only in cases where a single expression is to be evaluated, and where the code for the function looks better if it’s closer to where the function is to be used. If you find yourself trying to think of some convoluted trick to get a lambda function to work in a particular situation, you’re probably better off using the regular `def`

function.

## The map() function

The built-in `map()`

function takes two arguments: `map(<function>,<list>)`

. The `map()`

function applies the <function> to each element of the <list> (We can also use a tuple as the second argument). To view the results, you need to cast the call to map to a list or tuple. For example:

from math import * roots = list(map(sqrt, list(range(10)))) print(roots)

This applies the `sqrt()`

function in the math module to each element of the list of ints from 0 to 9. The output is a list of these square roots.

Although `map()`

can be used with built-in functions (such as `sqrt()`

here) and with user-defined functions using `def`

, it is commonly used with lambda functions. This is another case where it is not necessary to name a lambda function in order to use it. For example, if we wanted a list of squares, we could write:

squares = list(map(lambda x: x ** 2, list(range(10)))) print(squares)

The output is [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]. (We could do the same thing using a for loop, but this would take a bit more code.)

## The filter() function

The built-in `filter()`

function also takes a function and a list as arguments, so its syntax is `filter(<function>, <list>)`

. This time, the <function> must be a boolean test that is performed on each member of <list>. `filter()`

then returns a collection of elements of <list> for which the test evaluates to True. For example

odds = tuple(filter(lambda x: x % 2 != 0, list(range(10)))) print(odds)

The list of ints from 0 to 9 is tested with the condition `x % 2 != 0`

. If the condition is True, the int is odd and is included in the output; if False, the int is even and excluded. The output is (1, 3, 5, 7, 9). I’ve cast the output of `filter()`

to a tuple rather than a list just to show that either is possible.

We can combine `map()`

and `filter()`

. For example, suppose we wanted a list of the odd squares of the ints from 0 to 9. We could write:

oddSquares = list(filter(lambda x: x % 2 != 0, list(map(lambda x: x ** 2, list(range(10)))))) print(oddSquares)

The list using `map()`

produces the squares of the ints from 0 to 9, as before, and this list is fed into the `filter()`

which selects the odd numbers. Due to the proliferation of parenthses, it might be better to split this up into two separate calls, however, but it serves to illustrate the uses of `filter()`

and `map(`

).

## The reduce() function

Our final example of a function that is useful with lambda functions is the `reduce()`

function. Unlike `map()`

and `filter()`

, `reduce()`

is *not* a built-in function, so you need to import it from the `functools`

module.

`reduce()`

takes the same two arguments as `map()`

and `filter()`

: a function and a list or tuple, so its syntax is `reduce(<function>, <list>)`

. This time, the <function> must take exactly two arguments, and the elements of the list are fed pairwise into the function. For example, we can use `reduce()`

to sum up all the elements in a tuple, as follows:

from functools import reduce sum = reduce(lambda a, b: a + b, tuple(range(1, 101))) print(sum)

This applies `a + b`

successively to each pair of elements in the list. Thus it starts with 1 + 2 = 3, then applies the result to the next element, so we have 3 + 3 = 6, then 6 + 4 = 10 and so on. The result here is the sum of the integers from 1 to 100, which is 5050.

## Exercise

Look up on the web the formulas for the volumes of the five regular Platonic solids (tetrahedron, cube, octahedron, dodecahedron and icosahedron). Write a program in which these volume formulas are stored as lambda functions in a dictionary with keys describing the corresponding solid.

The program should then ask the user for a number of solids to generate. Each solid should be chosen at random from one of the five types, and should have a side length chosen at random in the interval [0, 10]. Create a list of all the generated solids, where the data for each solid is stored as a tuple within the list.

Once you have the complete list of solids, use lambda functions together with the `map()`

, `filter()`

and `reduce()`

functions, as appropriate, to calculate the following:

- The number of solids of each of the five types
- The total volume of the solids in each of the five types
- The total volume of all the solids

The volumes are:

tetrahedron | |

cube | |

octahedron | |

dodecahedron | |

icosahedron |

One version of the program which answers the question is:

from math import * from random import * from functools import reduce volume = { 'tetrahedron': lambda x: x ** 3 / (6 * sqrt(2)), 'cube': lambda x: x ** 3, 'octahedron': lambda x: sqrt(2) * x ** 3 / 3, 'dodecahedron': lambda x: (15 + 7*sqrt(5)) * x ** 3 / 4, 'icosahedron': lambda x: 5 * (3 + sqrt(5)) * x ** 3 / 12 } solids = list(volume.keys()) maxSide = 10 while True: numSolids = input('How many solids (or "quit")?') if numSolids == 'quit': break numSolids = int(numSolids) solidList = [] for i in range(numSolids): solidType = solids[randint(0, len(solids) - 1)] sideLength = random() * maxSide solidList += [tuple((solidType, volume[solidType](sideLength)))] solidTypes = {} totalVolume = 0 for t in solids: solidTypes[t] = list(filter(lambda x: x[0] == t, solidList)) numType = len(solidTypes[t]) print('Number of ' + t + 's:', numType) if numType > 0: volType = reduce(lambda a, b: a + b, list(map(lambda x: x[1], solidTypes[t]))) print('Volume of ' + t + 's:', volType) totalVolume += volType print('Total volume =', totalVolume)

The `volume`

dictionary uses the same technique as the `area`

dictionary in the previous example. In order to choose a solid type at random, we need a list of the types, which is given on line 11 as a list of the keys in the volume dictionary.

The list of tuples representing the randomly chosen solids is generated in lines 19 through 24. The volume is calculated on line 24 by retrieving the correct lambda function from the `volume`

dictionary and calling it with `sideLength`

as the argument.

To calculate the number of each type of solid, we initialize a dictionary called `solidTypes`

on line 26. The for loop on line 28 loops over each solid type (remember this is the list of keys created on line 11). We filter out those solids in the master list (`solidList`

) by using the `filter()`

function which acts on each tuple from `solidList`

. The [0] element of each tuple stores the solid’s type, so we filter out those solids whose type matches the value `t`

that we’re currently processing. This creates a list of solids of that particular type, which is stored in `solidTypes[t]`

. The number of solids of that type is then the length of this list, calculated on line 30.

If there is more than one solid of a given type (checked on line 32), we use the `map()`

function to create a list of volumes for that type of solid. The volume of a solid is stored as the [1] element in its tuple, so the lambda function within the `map()`

function on line 33 selects that element, thus creating a list of volumes for that element. We then pass this list to the `reduce()`

function, also on line 33, which sums up the volumes for that solid type.