# An idea for fast function composition

Sat Feb 16 22:47:51 CET 2008

```Hi all,

this was probably not the first).  The fast way to create a
(anonymous) composite function

f1 o f2 o ... o fn

in Python is via

lambda x: f1(f2(...fn(x)...)),

but according to some this is neither the most compact nor the most
readable.  Below I define a 'compose' function such that the above can
be written

compose(f1, f2, ...., fn),

the resulting function being as fast as the lambda version (or maybe
faster?).  'getcomposer' is a helper function (which in most cases
will amount to a dictionary lookup).

----------------------------------
def getcomposer(nfunc, _cache={}):
"getcomposer(n) -> lambda f1, ..., fn:(lambda x: f1(...fn(x)...))"
try:
return _cache[nfunc]
except KeyError:
fnames = ['f%s' % i for i in range(nfunc)]
call = ''.join('%s(' % f for f in fnames)
args = ','.join(fnames)
cstr = 'lambda %s:(lambda x:%sx%s)' % (args, call, ')'*nfunc)
composer = _cache[nfunc] = eval(cstr)
return composer

def compose(*functions):
"compose(f1, ..., fn) -> lambda x: f1(f2(...fn(x)...))"
return getcomposer(len(functions))(*functions)

# Test

def double(x): return 2*x
def square(x): return x**2
def succ(x): return x+1

f1 = compose(double, square, succ, float)
f2 = lambda x: double(square(succ(float(x))))

def benchmark(f, n=1000000):
from time import time
from itertools import imap
t0 = time()
for _ in imap(f1, xrange(n)): pass
t1 = time()
return t1-t0

print 'compose', benchmark(f1)
print 'lambda ', benchmark(f2)
----------------------------------

marigold:python arno\$ python -i simple_compose.py
compose 1.84630298615
lambda  1.86365509033
>>> import dis
>>> dis.dis(f1)
15 CALL_FUNCTION            1
18 CALL_FUNCTION            1
21 CALL_FUNCTION            1
24 CALL_FUNCTION            1
27 RETURN_VALUE
>>> dis.dis(f2)
15 CALL_FUNCTION            1
18 CALL_FUNCTION            1
21 CALL_FUNCTION            1
24 CALL_FUNCTION            1
27 RETURN_VALUE

f1 and f2 are almost exaclty the same but array lookups (LOAD_DEREFs)
in f1 replace dictionary lookups (LOAD_GLOBALs) in f2.  A C version of
'compose' could easily be written that doesn't require the use of a
python lambda-function (as created by 'getcomposer').

--
Arnaud

```