I l@ve RuBoard Previous Section Next Section

15.9 Composing Functions

Credit: Scott David Daniels

15.9.1 Problem

You need to construct a new function by composing existing functions (i.e., each call of the new function must call one existing function on its arguments, then another on the result of the first one).

15.9.2 Solution

Composition is a fundamental operation between functions that yields a new function as a result梩he new function must call one existing function on its arguments, then another on the result of the first one. For example, a function that, given a string, returns a copy that is lowercase and does not have leading and trailing blanks, is the composition of the existing string.lower and string.trim functions (in this case, it does not matter in which order the two existing functions are applied, but generally, it could be important). A class defining the special method _ _call_ _ is often the best Pythonic approach to constructing new functions:

class compose:
    '''compose functions. compose(f,g,x...)(y...) = f(g(y...),x...))'''
    def _ _init_ _(self, f, g, *args, **kwargs):
        self.f = f
        self.g = g
        self.pending = args[:]
        self.kwargs = kwargs.copy(  )

    def _ _call_ _(self, *args, **kwargs):
        return self.f(self.g(*args, **kwargs), *self.pending, **self.kwargs)

class mcompose(compose):
    '''compose functions. mcompose(f,g,x...)(y...) = f(*g(y...),x...))'''
    TupleType = type((  ))

    def _ _call_ _(self, *args, **kwargs):
        mid = self.g(*args, **kwargs)
        if isinstance(mid, self.TupleType):
            return self.f(*(mid + self.pending), **self.kwargs)
        return self.f(mid, *self.pending, **self.kwargs)

15.9.3 Discussion

The two classes in this recipe show two styles of function composition. The only difference is when the second function, g, returns a tuple. compose passes the results of g as f's first argument anyway, while mcompose treats them as a tuple of arguments to pass along. Note that the extra arguments provided for compose or mcompose are treated as extra arguments for f (as there is no standard functional behavior to follow here):

compose(f,g, x...)(y...) = f(g(y...), x...)
mcompose(f,g, x...)(y...) = f(*g(y...), x...)

As in currying (see Recipe 15.8), this recipe's functions are for constructing functions from other functions. Your goal should be clarity, since there is no efficiency gained by using the functional forms.

Here's a quick example for interactive use:

parts = compose(' '.join, dir)

When applied to a module, the callable we just bound to parts gives you an easy-to-view string that lists the module's contents.

I separated mcompose and compose because I think of the two possible forms of function composition as being quite different. However, inheritance comes in handy for sharing the _ _init_ _ method, which is identical in both cases. Class inheritance, in Python, should not be thought of as mystical. Basically, it's just a lightweight, speedy way to reuse code (code reuse is good, code duplication is bad).

In Python 2.2 (or 2.1 with from _ _future_ _ import nested_scopes), there is a better and more concise alternative that uses closures in lieu of class instances. For example:

def compose(f, g, *orig_args, **orig_kwds):
    def nested_function(*more_args, **more_kwds):
        return f(g(*more_args, **more_kwds), *orig_args, **orig_kwds)
    return nested_function

This compose function is substantially equivalent to, and roughly interchangeable with, the compose class presented in the solution.

15.9.4 See Also

Recipe 15.8 for an example of currying (i.e., associating parameters with partially evaluated functions).

    I l@ve RuBoard Previous Section Next Section