We're hiring!

We're actively seeking developers and designers for our Ann Arbor & Detroit locations.

Writing Functional Python

Functional programming has a lot of advantages that have been enumerated over the years. Being a bit of a Pythonista, myself, I can’t help but introduce functional concepts into my Python code. Though Python is not a functional language, it doesn’t mean that your Python code can’t benefit from some of these concepts.

Perhaps one of my favorite illustrations of functional Python comes in the treatment of loops — or more accurately, their removal from my code. Before I demonstrate how to do that, allow me to introduce a very contrived example.

Let’s say you’ve parsed a configuration file into a list of lists, and you need to be able to access a particular settings by keyword. The following snippet does exactly that in a traditional format.

configurations = [["name","Bob"], ["email","bob@roberts.com"], ["template","dark"]]
 
def get_config_value(desired_key, configs):
    for key_value in configs:
        if key_value[0] == desired_key:
            return key_value[1]
print get_config_value("name", configurations)

This code works well, but I find the nested statements less than ideal.

Functional Alternative #1 – Generators

Generators simply create iterators (i.e., something you can use in a loop) without constructing a complete list ahead of time. Generators are lazy loaded and are more efficient than their eager-loading cousin, the list comprehension, when working with large data sets.

The following returns the same result as the previous example with one less layer of nesting:

def get_config_value(desired_key, configs):
    for value in (config[1] for config in configs if config[0] == desired_key):
        return value
 
print get_config_value("name", configurations)

I often use generator expressions when I’m only working with a single, simple list. They’re built-in, simple to create, and, more importantly, Pythonic.

Functional Alternative #2 – Map and Filter

But when my code is a bit more involved and I find I’m working with a number of lists with more complexity, generator expressions can become somewhat unreadable. To combat this, I can turn to the imap and ifilter functions in the itertools module (in Python 3, the built in map and filter functions are equivalent).

Though without using an abstraction, as the next example illustrates, they really don’t improve readability.

import itertools
 
def get_config_value(desired_key, configs):
    for value in itertools.imap(lambda item:item[1],
        itertools.ifilter(lambda item:item[0]==desired_key, configs)):
        return value
 
print get_config_value("name", configurations)

Functional Alternative #3 – IterHelper

To address this, let’s create the IterHelper class.

import itertools
 
class IterHelper(object):
    def __init__(self, iterable = None):
        self.iterable = iterable
 
    def map(self, func):
        return IterHelper(itertools.imap(func, self.iterable))
 
    def filter(self, predicate):
        return IterHelper(itertools.ifilter(predicate, self.iterable))
 
    def first(self):
        for item in self.iterable:
            return item

That abstraction allows me to write the following cleaner code.

def get_config_value(desired_key, configs):
    return IterHelper(configs)\
        .filter(lambda item:item[0] == desired_key)\
        .map(lambda item:item[1])\
        .first()
 
print get_config_value("name", configurations)

Of course, if you don’t want to write all of that extra code, you could simply use the ASQ module, a port of Microsoft’s LINQ. However, it’s useful to be able to create you own version since you can both understand what is happening in the background and define a grammar tailored to your specific needs.

However you choose to handle iterating, you should keep in mind that there are options when your code is no longer looking quite as Pythonic as you would like to be.

Happy coding!

Darrell Hawley (7 Posts)

This entry was posted in Functional Programming and tagged . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

5 Comments

  1. chris czub
    Posted April 11, 2014 at 2:39 pm

    great post!

    looks like there’s an indentation error in the first example — i assume the ‘return’ statement was intended to be indented.

  2. Posted April 14, 2014 at 10:20 am

    You should hang out with the Grand Rapids Python Users Group some time! http://www.meetup.com/grpython/

    • Posted April 14, 2014 at 8:00 pm

      Thank you for the invite, Ben. I’ll try to make sure my next trip to GR coincides with GRPython.

  3. Ben Rousch
    Posted April 14, 2014 at 9:01 pm

    Ah yeah, I forgot that Atoms are not all in GR these days.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>