Credit: Scott David Daniels
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).
Composition is a fundamental
operation between functions that yields a new function as a
result—the 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)
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.
Recipe 15.8 for an example of currying (i.e., associating parameters with partially evaluated functions).