Chapter 9. Functors

In Python a function object is an object reference to any callable, such as a function, a lambda function, or a method. The definition also includes classes, since an object reference to a class is a callable that, when called, returns an object of the given class—for example, x = int(5). In computer science a functor is an object that can be called as though it were a function, so in Python terms a functor is just another kind of function object. Any class that has a __call__() special method is a functor. The key benefit that functors offer is that they can maintain some state information. For example, we could create a functor that always strips basic punctuation from the ends of a string. We would create and use it like this:

strip_punctuation = Strip(",;:.!?")
strip_punctuation("Land ahoy!")       # returns: 'Land ahoy'


Here we create an instance of the Strip functor initializing it with the value ",;:.!?". Whenever the instance is called it returns the string it is passed with any punctuation characters stripped off. Here’s the complete implementation of the Strip class:

class Strip:

    def __init__(self, characters):
        self.characters = characters

    def __call__(self, string):
        return string.strip(self.characters)


We could achieve the same thing using a plain function or lambda, but if we need to store a bit more state or perform more complex processing, a functor is often the right solution.

A functor’s ability to capture state by using a class is very versatile and powerful, but sometimes it is more than we really need. Another way to capture state is to use a closure. A closure is a function or method that captures some external state. For example:

def make_strip_function(characters):
    def strip_function(string):
        return string.strip(characters)
    return strip_function

strip_punctuation = make_strip_function(",;:.!?")
strip_punctuation("Land ahoy!")       # returns: 'Land ahoy'


The make_strip_function() function takes the characters to be stripped as its sole argument and returns a function, strip_function(), that takes a string argument and which strips the characters that were given at the time the closure was created. So just as we can create as many instances of the Strip class as we want, each with its own characters to strip, we can create as many strip functions with their own characters as we like.

The classic use case for functors is to provide key functions for sort routines. Here is a generic SortKey functor class (from file SortKey.py):

class SortKey:

    def __init__(self, *attribute_names):
        self.attribute_names = attribute_names

    def __call__(self, instance):
        values = []
        for attribute_name in self.attribute_names:

            values.append(getattr(instance, attribute_name))
        return values


When a SortKey object is created it keeps a tuple of the attribute names it was initialized with. When the object is called it creates a list of the attribute values for the instance it is passed—in the order they were specified when the SortKey was initialized. For example, imagine we have a Person class:

class Person:

    def __init__(self, forename, surname, email):
        self.forename = forename
        self.surname = surname
        self.email = email


Suppose we have a list of Person objects in the people list. We can sort the list by surnames like this: people.sort(key=SortKey("surname")). If there are a lot of people there are bound to be some surname clashes, so we can sort by surname, and then by forename within surname, like this: people.sort(key=SortKey("surname", "forename")). And if we had people with the same surname and forename we could add the email attribute too. And of course, we could sort by forename and then surname by changing the order of the attribute names we give to the SortKey functor.

Another way of achieving the same thing, but without needing to create a functor at all, is to use the operator module’s operator.attrgetter() function. For example, to sort by surname we could write: people.sort(key=operator.attrgetter("surname")). And similarly, to sort by surname and forename: people.sort(key=operator.attrgetter("surname", "forename")). The operator. attrgetter() function returns a function (a closure) that, when called on an object, returns those attributes of the object that were specified when the closure was created.

Functors are probably used rather less frequently in Python than in other languages that support them because Python has other means of doing the same things—for example, using closures or item and attribute getters.

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

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