Velocity Reviews > Pythonic function composition

# Pythonic function composition

Alan G Isaac
Guest
Posts: n/a

 10-25-2004
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

Peter Otten
Guest
Posts: n/a

 10-25-2004
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

Oliver Fromme
Guest
Posts: n/a

 10-25-2004
Alan G Isaac <(E-Mail Removed)> 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)

Alan G Isaac
Guest
Posts: n/a

 10-25-2004

"Peter Otten" <(E-Mail Removed)> wrote in message
news:clj5nq\$jl\$02\$(E-Mail Removed)-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
Guest
Posts: n/a

 10-25-2004
"Oliver Fromme" <(E-Mail Removed)> wrote in message
news:(E-Mail Removed)...
> 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

Michael J. Fromberger
Guest
Posts: n/a

 10-25-2004
In article <(E-Mail Removed)>,
"Alan G Isaac" <(E-Mail Removed)> 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

Lonnie Princehouse
Guest
Posts: n/a

 10-25-2004

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)

 Posting Rules You may not post new threads You may not post replies You may not post attachments You may not edit your posts BB code is On Smilies are On [IMG] code is On HTML code is OffTrackbacks are On Pingbacks are On Refbacks are Off Forum Rules

 Similar Threads Thread Thread Starter Forum Replies Last Post Arnaud Delobelle Python 4 02-17-2008 12:07 AM Kay Schluehr Python 10 02-04-2008 05:08 PM Tom Moertel Ruby 2 04-07-2006 07:10 PM Carl J. Van Arsdall Python 4 02-07-2006 10:15 PM Edgardo Hames Ruby 7 08-07-2004 09:31 AM