Chapter 16. Functional-Style Programming

Functional-style programming is an approach to programming where computations are built up from combining functions that don’t modify their arguments and that don’t refer to or change the program’s state, and that provide their results as return values. One strong appeal of this kind of programming is that (in theory), it is much easier to develop functions in isolation and to debug functional programs. This is helped by the fact that functional programs don’t have state changes, so it is possible to reason about their functions mathematically.

Three concepts that are strongly associated with functional programming are mapping, filtering, and reducing. Mapping involves taking a function and an iterable and producing a new iterable (or a list) where each item is the result of calling the function on the corresponding item in the original iterable. This is supported by the built-in map() function, for example:

list(map(lambda x: x ** 2, [1, 2, 3, 4]))   # returns: [1, 4, 9, 16]


The map() function takes a function and an iterable as its arguments and for efficiency it returns an iterator rather than a list. Here we forced a list to be created to make the result clearer:

[x ** 2 for x in [1, 2, 3, 4]]              # returns: [1, 4, 9, 16]


A generator expression can often be used in place of map(). Here we have used a list comprehension to avoid the need to use list(); to make it a generator we just have to change the outer brackets to parentheses.

Filtering involves taking a function and an iterable and producing a new iterable where each item is from the original iterable—providing the function returns True when called on the item. The built-in filter() function supports this:

list(filter(lambda x: x > 0, [1, -2, 3, -4])) # returns: [1, 3]


The filter() function takes a function and an iterable as its arguments and returns an iterator.

[x for x in [1, -2, 3, -4] if x > 0]          # returns: [1, 3]


The filter() function can always be replaced with a generator expression or with a list comprehension.

Reducing involves taking a function and an iterable and producing a single result value. The way this works is that the function is called on the iterable’s first two values, then on the computed result and the third value, then on the computed result and the fourth value, and so on, until all the values have been used. The functools module’s functools.reduce() function supports this. Here are two lines of code that do the same computation:

functools.reduce(lambda x, y: x * y, [1, 2, 3, 4])  # returns: 24
functools.reduce(operator.mul, [1, 2, 3, 4])          # returns: 24


The operator module has functions for all of Python’s operators specifically to make functional-style programming easier. Here, in the second line, we have used the operator.mul() function rather than having to create a multiplication function using lambda as we did in the first line.

Python also provides some built-in reducing functions: all(), which given an iterable, returns True if all the iterable’s items return True when bool() is applied to them; any(), which returns True if any of the iterable’s items is True; max(), which returns the largest item in the iterable; min(), which returns the smallest item in the iterable; and sum(), which returns the sum of the iterable’s items.

Now that we have covered the key concepts, let us look at a few more examples. We will start with a couple of ways to get the total size of all the files in list files:

functools.reduce(operator.add, (os.path.getsize(x) for x in files))
functools.reduce(operator.add, map(os.path.getsize, files))


Using map() is often shorter than the equivalent list comprehension or generator expression except where there is a condition. We’ve used operator.add() as the addition function instead of lambda x, y: x + y.

If we only wanted to count the .py file sizes we can filter out non-Python files. Here are three ways to do this:

functools.reduce(operator.add, map(os.path.getsize,
                 filter(lambda x: x.endswith(".py"), files)))
functools.reduce(operator.add, map(os.path.getsize,
                 (x for x in files if x.endswith(".py"))))
functools.reduce(operator.add, (os.path.getsize(x)
                 for x in files if x.endswith(".py")))


Arguably, the second and third versions are better because they don’t require us to create a lambda function, but the choice between using generator expressions (or list comprehensions) and map() and filter() is most often purely a matter of personal programming style.

Using map(), filter(), and functools.reduce() often leads to the elimination of loops, as the examples we have seen illustrate. These functions are useful when converting code written in a functional language, but in Python we can usually replace map() with a list comprehension and filter() with a list comprehension with a condition, and many cases of functools.reduce() can be eliminated by using one of Python’s built-in functional functions such as all(), any(), max(), min(), and sum(). For example:

sum(os.path.getsize(x) for x in files if x.endswith(".py"))


This achieves the same thing as the previous three examples, but is much more compact.

In addition to providing functions for Python’s operators, the operator module also provides the operator.attrgetter() and operator.itemgetter() functions, the first of which we briefly met earlier in this short cut. Both of these return functions which can then be called to extract the specified attributes or items.

Whereas slicing can be used to extract a sequence of part of a list, and slicing with striding can be used to extract a sequence of parts (say, every third item with L[::3]), operator.itemgetter() can be used to extract a sequence of arbitrary parts, for example, operator.itemgetter(4, 5, 6, 11, 18)(L). The function returned by operator.itemgetter() does not have to be called immediately and thrown away as we have done here; it could be kept and passed as the function argument to map(), filter(), or functools.reduce(), or used in a dictionary, list, or set comprehension.

When we want to sort we can specify a key function. This function can be any function, for example, a lambda function, a built-in function or method (such as str.lower()), or a function returned by operator.attrgetter(). For example, assuming list L holds objects with a priority attribute, we can sort the list into priority order like this: L.sort(key=operator.attrgetter("priority")).

In addition to the functools and operator modules already mentioned, the iter-tools module can also be useful for functional-style programming. For example, although it is possible to iterate over two or more lists by concatenating them, an alternative is to use itertools.chain() like this:

for value in itertools.chain(data_list1, data_list2, data_list3):
    total += value


The itertools.chain() function returns an iterator that gives successive values from the first sequence it is given, then successive values from the second sequence, and so on until all the values from all the sequences are used. The itertools module has many other functions and its documentation gives many small yet useful examples and is well worth reading.

16.1. Partial Function Application

Partial function application is the creation of a function from an existing function and some arguments to produce a new function that does what the original function did, but with some arguments fixed so that callers don’t have to pass them. Here’s a very simple example:

enumerate1 = functools.partial(enumerate, start=1)
for lino, line in enumerate1(lines):
    process_line(i, line)


The first line creates a new function, enumerate1(), that wraps the given function (enumerate()) and a keyword argument (start=1) so that when enumerate1() is called it calls the original function with the fixed argument—and with any other arguments that are given at the time it is called, in this case lines. Here we have used the enumerate1() function to provide conventional line counting starting from line 1.

Using partial function application can simplify our code, especially when we want to call the same functions with the same arguments again and again. For example, instead of specifying the mode and encoding arguments every time we call open() to process UTF-8 encoded text files, we could create a couple of functions with these arguments fixed:

reader = functools.partial(open, mode="rt", encoding="utf8")
writer = functools.partial(open, mode="wt", encoding="utf8")


Now we can open text files for reading by calling reader(filename) and for writing by calling writer(filename).

One very common use case for partial function application is in GUI (Graphical User Interface) programming, where it is often convenient to have one particular function called when any one of a set of buttons is pressed. For example:

loadButton = tkinter.Button(frame, text="Load",
                        command=functools.partial(doAction, "load"))
saveButton = tkinter.Button(frame, text="Save",
                        command=functools.partial(doAction, "save"))


This example uses the tkinter GUI library that comes as standard with Python. The tkinter.Button class is used for buttons—here we have created two, both contained inside the same frame, and each with a text that indicates its purpose. Each button’s command argument is set to the function that tkinter must call when the button is pressed, in this case the doAction() function. We have used partial function application to ensure that the first argument given to the doAction() function is a string that indicates which button called it so that doAction() is able to decide what action to perform.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset