Python evita la lambda para la clave que necesita dos callables (composición de la función)

Estaba trabajando en el problema 14 del proyecto Euler y, como primer bash, preparé esta solución de fuerza bruta:

def collatz(n, memo={1: [1]}): if n not in memo: memo[n] = [n] + collatz(3 * n + 1 if n % 2 else n // 2) return memo[n] def p014(): return max(xrange(1, 10**6), key=lambda n: len(collatz(n))) 

Mi pregunta es acerca de esa lambda, por lo general soy reacio a usarlos, pero no conozco ninguna forma elegante de evitarlo en este caso. ¿Hay algo en functools u otro para encadenar dos callables, o alguna otra alternativa limpia que me esté perdiendo?

Sería encantador si hubiera una función de compose , tal vez en functools . No hay, ni espero que haya, por desgracia . En las palabras de Raymond Hettinger,

Ha sido discutido previamente y rechazado en otros foros. Uno de los problemas es que el orden matemático habitual no es intuitivo y no se autodocumenta, es decir, ¿ compose(f,g) lo mismo que f(g(x)) o g(f(x)) ? Además, ya es muy sencillo crear su propia función de composición o hacer la composición directamente: h = lambda x: f(g(x)) .

Aquí hay dos implementaciones simples de compose como una clase invocable que puede encontrar útil, sin embargo:

 # Scott Daniels, http://code.activestate.com/recipes/52902-function-composition/ # Lightly edited for style. class Compose(object): '''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 Starcompose: '''Compose functions. Starcompose(f,g,x...)(y...) = f(*g(y...),x...))''' TupleType = type(()) 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): 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) 

También, vea el paquete functional , que inspiró esta muy simple función compose_many mía hace un tiempo:

 def compose(f1, f2): def composition(*args, **kwargs): return f1(f2(*args, **kwargs)) return composition def compose_many(*funcs): return reduce(compose, funcs) 

Podría ser más sintético escribir esto como un generador:

 def p014(): length, n = max( (len(collatz(n)), n) for n in xrange(1, 10**6) ) return n