hat’s Power Python, you ask? It’s the effective use of Python language features to get a lot of work done in fewer lines of code. The lambda, reduce, filter, map, and list comprehension constructs are some of the features that best fit this definition. This article examines each one in turn and highlights some of their strengths with simple code examples that illustrate the basic syntax and usage. It then delves into some related coding advantages that Python offers, such as the easy creation of callbacks and closures.
The built-in Python functions discussed adhere to some of the basic principles of Functional Programming (FP). While this article doesn’t focus on the philosophy of FP per se, it does highlight some of the advantages of the FP style of programming, such as the emphasis on list processing, the use of functions to replace looping, the avoidance of side effects, and the reliance on functions being first class objects. By the end of the article, you should have a strong grasp of how you can use these powerful techniques to write more expressive and concise code in your projects.
Author’s Note: Examples in the text assume the reader is at least an advanced beginner in Python programming.
The lambda construct allows you to create anonymous functions on the fly. Since methods are first-class objects in Python, you can pass anonymous functions around as arguments to a method. Practically speaking, because methods are first-class objects, you can assign a method to a variable just as you would an instance of a class. (If you’ve got a C background, passing a method in this manner is essentially the same as a pointer to a function.) This ability to use method objects as arguments to a function makes it simple to implement callbacks.
Figure 1 illustrates the syntax and usage of the lambda.
|Figure 1: Syntax and Usage of the Lambda Construct|
Though the example in Figure 1 is trivial, you can see that using lambda eliminates the need for an additional if conditional block. In more complex code, consolidating conditional logic into small helper lambda functions (that you don’t need to define as methods in their own right) can make code more structured and readable.
If the concept of a callback is unfamiliar to you, consider another example: the Python standard library’s
os.path.walk(root, function, args ) method. The
walk() function uses the passed argument (args) to apply the method’s function argument as the code recursively iterates through a directory tree starting at the specified root. This repeated calling out (or back) to the same function while executing the loop may be how the technique got the name callback, but don’t quote me on that.
You’ll find a practical implementation of the
walk( ) method in a previously published DevX article I wrote Python Boosts Jar Auditor Functionality. The auditor utility highlighted in that article presents another useful Python feature as well: the ability to create closures. Objects are data bundled with behavior (methods); closures are methods bundled with some data. Python enables you to create closures by allowing methods to set initial argument values in the method definition itself (see Figure 2).
|Figure 2: Create Closures in Python|
In the Figure 2 snippet, Python presets the y value. This way, if a value isn’t passed to the method (under some condition), the method uses the value set in the method definition. This adds great flexibility to the way methods are called. This isn’t just syntactic sugar. The added expressive power, depending upon the circumstances, can simplify or reduce the coded logic required to set the argument(s) that need to be passed.
map(function, list ) function applies a function to a list of values. If you’re thinking: ‘hey, that sounds like a for loop‘, you’re correct. FP eschews using transient variables as counters in favor of constructs like
map( ) (see Figure 3).
In FP, side effects is the term for variables that aren’t part of the work a function does but rather are helpers required (in this case) to make the loop work. An obvious benefit of reducing or eliminating side effects is that it reduces the number of lines of code, which means fewer bugs. While having fewer bugs (due to improperly initialized variables, counter variables that are not reset or are shadowed, etc.) is certainly a worthwhile benefit, I’m more focused on the code’s conciseness.
David Mertz succinctly expresses the benefits of the FP approach by saying that the line
map(mappableFunction, namelist) represents a single programmatic thought. The intent of the code is to “apply the function across the namelist variable’s value”—one thought: do this to that. One line of code corresponds to that one thought. Granted you’ll face some setup (e.g., in the method definition, etc.), but the actual call to do the work—the essence of the this code block’s intent—is clean and concise.
filter( ) function works in a similar manner to
map( ). The differences lie in what it returns. The
map( ) function returns a list of the same length as the list that is passed as an argument to it. The aptly named
filter( ) function returns only those values from the list that meet the conditions of the function that is applied. In other words, it filters out values from the input list (see Figure 4).
At this point, I hope a light bulb or two are flashing in your mind. You might even be thinking that you could nest these functions inside each other to filter desired variables out of a list and then map a function against this list. This is certainly possible, but I’d caution against overzealous nesting with these constructs. One reason is that when you nest these constructs you can’t easily pick out intermediate values, and this could make debugging a headache. More significantly, the most conspicuous benefit of these functions is their clear translation of thought to code. An overabundance of nested functions would greatly reduce your code’s readability and maintainability.
reduce(afunction, alist) function is similar to
map( ) and
filter( ) in its signature. However,
reduce() serves to aggregate the members of a list, typically to return a single value.
Imagine that you are assembling URLs dynamically for a list of article links. The parts of the URL are in a list that specifies the location, the path where they will reside on the host, and finally, the file name (see Figure 5). (This is only an example. This article is exclusively available on DevX.com).
|Figure 5: Assembling URLs Dynamically for a List|
If you’re unfamiliar with the operator module, it is mostly composed of the basic Python operators converted into a function form. While a function version of an operator isn’t going to perform better than the built-in operator, it does have its advantages for certain types of operations. In the Figure 5 example,
join(url_parts,'') would have worked just as well.
The list comprehension construct is really just syntactic sugar to make operations that work on lists simpler to write and understand. It fits with the other functional constructs because it expresses the iteration through the members of a list as a single thought in one line of code (see Figure 6).
|Figure 6: List Comprehension Loop|
The loop in Figure 6 reads as follows: “Return a list of two element tuples where the
multiplier(x,y) function results in a value greater than 25 using the combination of inputs from the tuples (1, 4, 6, 24, 19) and (15, 7, 1, 2).”
Although this syntax may be unfamiliar to you, hopefully you recognize that the example list comprehension just built a concise nested for loop. The advantages here are:
- Fewer lines of code reduce the opportunity to introduce a bug.
- The code is arguably simpler to write since no indentations are required and the extent of the loop is delimited by the square brackets ( [ ] ).
- The return value is stated up front (the tuple (x,y) at the start of the line), which eliminates the need to determine which form the output result of the loop will take.
On the flip side, while this syntax enables a programmer to write the loop in a single line, it perhaps appears more cluttered to somebody unfamiliar with list comprehensions, which could impact maintainability. Also, it is more difficult to insert intermediate debug print statements.
Do More with Less
Python offers some elegant constructs that enable you to write clearer, more concise code to get the job done. As with any coding technique, misuse will undermine the readability and maintainability of your code. However, properly implemented, these techniques offer a way to more directly translate your thoughts into clean, concise, and expressive code. Of course, you should use the right tool for the job, but if the job is a fit for Python, then these techniques will enable you to do more with less.