# Pythonic function composition

Discussion in 'Python' started by Alan G Isaac, Oct 25, 2004.

1. ### Alan G IsaacGuest

Given a list of functions, it seems there must be a
Pythonic approach to composition. Something like

def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)

This will not work because the argument 'x' is not "inside".
What is the proper formulation?

Thanks,
Alan Isaac

Alan G Isaac, Oct 25, 2004

2. ### Peter OttenGuest

Alan G Isaac wrote:

> Given a list of functions, it seems there must be a
> Pythonic approach to composition. Something like
>
> def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
>
> This will not work because the argument 'x' is not "inside".
> What is the proper formulation?

You need to pass a function that makes a function (a "factory") to reduce():

>>> fns = [lambda x: x+2, lambda x: x*2, lambda x: x*x]
>>> def compose(f, g):

.... def fog(x):
.... return f(g(x))
.... return fog
....
>>> g1 = reduce(compose, fns)
>>> g1(2)

10

The same with lambdas:

>>> g2 = reduce(lambda f, g: lambda x: f(g(x)), fns)
>>> g2(2)

10

Peter

Peter Otten, Oct 25, 2004

3. ### Oliver FrommeGuest

Alan G Isaac <> wrote:
> Given a list of functions, it seems there must be a
> Pythonic approach to composition. Something like
>
> def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
>
> This will not work because the argument 'x' is not "inside".
> What is the proper formulation?

There are probably several ways to do it.
The following is the one which come to my mind first.

The trick is to first define a function that composes
two functions, and then use reduce() to apply it to an
arbitrary number of functions.

>>> def compose2 (f, g):

.... def h (x):
.... return f(g(x))
.... return h
....
>>> def compose (fns):

.... return reduce(compose2, fns)
....

Some testing:

>>> def add42 (x): return x + 42

....
>>> def mul2 (x): return x * 2

....
>>> def sub5 (x): return x - 5

....
>>> def div3 (x): return x / 3

....
>>> a = compose((add42, mul2, sub5, div3))
>>> a(1)

27

There's no need to juggle with lambda in this case.
Lambda has its uses, but this isn't one of them.

Best regards
Oliver

--
Oliver Fromme, Konrad-Celtis-Str. 72, 81369 Munich, Germany

``All that we see or seem is just a dream within a dream.''
(E. A. Poe)

Oliver Fromme, Oct 25, 2004
4. ### Alan G IsaacGuest

"Peter Otten" <> wrote in message
news:clj5nq\$jl\$02\$-online.com...
> >>> g2 = reduce(lambda f, g: lambda x: f(g(x)), fns)

Cool. That gets me there. I think
def compose(fns) : return reduce(lambda f, g: lambda x: f(g(x)), fns)
does exactly what I want.

Thanks,
Alan Isaac

Alan G Isaac, Oct 25, 2004
5. ### Alan G IsaacGuest

"Oliver Fromme" <> wrote in message
news:...
> There's no need to juggle with lambda in this case.
> Lambda has its uses, but this isn't one of them.

I kind of like the lambda version (see Peter's post).
But maybe it is more opaque.

Thanks,
Alan

Alan G Isaac, Oct 25, 2004
6. ### Michael J. FrombergerGuest

In article <>,
"Alan G Isaac" <> wrote:

> Given a list of functions, it seems there must be a
> Pythonic approach to composition. Something like
>
> def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
>
> This will not work because the argument 'x' is not "inside".
> What is the proper formulation?
>

If you are only concerned with unary functions, then composition is
fairly trivial to deal with:

def compose(*fns):
def id(x): return x

def c2(f, g):
def h(x): return f(g(x))
return h

return reduce(c2, fns, id)

However, if you want to deal with functions that may take multiple
arguments, you must be a little more clever. Here's one way that seems
to work okay:

def compose(*fns):
def id(*args): return args

def box(res):
if isinstance(res, (list, tuple)):
return res
else:
return (res,)

def unbox(res):
if isinstance(res, (list, tuple)) and len(res) == 1:
return res[0]
else:
return res

def c2(f, g):
def h(*args):
return unbox(f(*box(g(*args))))
return h

return reduce(c2, fns, id)

For instance:
def f1(a, b):
return (a / b, a % b)

def f2(a, b):
return a + b

def f3(a):
return a + 2

h = compose(f3, f2, f1)
h(5, 3)
==> 5

This will work, but it's not the most efficient possible solution. You
could defer unboxing until the end by defining another intermediate
function.

Cheers,
-M

--
Michael J. Fromberger | Lecturer, Dept. of Computer Science
http://www.dartmouth.edu/~sting/ | Dartmouth College, Hanover, NH, USA

Michael J. Fromberger, Oct 25, 2004
7. ### Lonnie PrincehouseGuest

def compose(f, *fns):
if not fns:
return f
else:
return lambda x: f(compose(*fns)(x))

....

>>> from math import *
>>> foo = compose(sin, sqrt, abs) # sin(sqrt(abs(x)))
>>> foo(-((pi/2.)**2))

1.0

....or you could try it this way, which makes some assumptions about
function names, but will possibly run faster:

def compose(*fns):
fnames = [f.__name__ for f in fns]
expr = "%s(x%s" % ('('.join(fnames),')'*len(fns))
return eval("lambda x: %s" % expr)

Lonnie Princehouse, Oct 25, 2004