python composition

plot title python



Componiendo funciones en python (10)

Implementación recursiva

Aquí hay una implementación recursiva bastante elegante, que usa las características de Python 3 para mayor claridad:

def strict_compose(*funcs): *funcs, penultimate, last = funcs if funcs: penultimate = strict_compose(*funcs, penultimate) return lambda *args, **kwargs: penultimate(last(*args, **kwargs))

Versión compatible con Python 2:

def strict_compose2(*funcs): if len(funcs) > 2: penultimate = strict_compose2(*funcs[:-1]) else: penultimate = funcs[-2] return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs))

Esta es una versión anterior que utiliza la evaluación perezosa de la recursión:

def lazy_recursive_compose(*funcs): def inner(*args, _funcs=funcs, **kwargs): if len(_funcs) > 1: return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1]) else: return _funcs[0](*args, **kwargs) return inner

Ambos parecen hacer una nueva tupla y dictado de argumentos en cada llamada recursiva.

Comparación de todas las sugerencias:

Probemos algunas de estas implementaciones y determinemos cuál es la más eficaz, primero algunas funciones de argumento único (Gracias, poke):

def square(x): return x ** 2 def increment(x): return x + 1 def half(x): return x / 2

Aquí están nuestras implementaciones, sospecho que mi versión iterativa es la segunda más eficiente (la composición manual naturalmente será la más rápida), pero esto puede deberse en parte a que evitó la dificultad de pasar cualquier número de argumentos o palabras clave entre funciones, en la mayoría de los casos Solo veremos el argumento trivial pasado.

from functools import reduce def strict_recursive_compose(*funcs): *funcs, penultimate, last = funcs if funcs: penultimate = strict_recursive_compose(*funcs, penultimate) return lambda *args, **kwargs: penultimate(last(*args, **kwargs)) def strict_recursive_compose2(*funcs): if len(funcs) > 2: penultimate = strict_recursive_compose2(*funcs[:-1]) else: penultimate = funcs[-2] return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs)) def lazy_recursive_compose(*funcs): def inner(*args, _funcs=funcs, **kwargs): if len(_funcs) > 1: return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1]) else: return _funcs[0](*args, **kwargs) return inner def iterative_compose(*functions): """my implementation, only accepts one argument.""" def inner(arg): for f in reversed(functions): arg = f(arg) return arg return inner def _compose2(f, g): return lambda *a, **kw: f(g(*a, **kw)) def reduce_compose1(*fs): return reduce(_compose2, fs) def reduce_compose2(*funcs): """bug fixed - added reversed()""" return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x)

Y para probar estos:

import timeit def manual_compose(n): return square(increment(half(n))) composes = (strict_recursive_compose, strict_recursive_compose2, lazy_recursive_compose, iterative_compose, reduce_compose1, reduce_compose2) print(''manual compose'', min(timeit.repeat(lambda: manual_compose(5))), manual_compose(5)) for compose in composes: fn = compose(square, increment, half) result = min(timeit.repeat(lambda: fn(5))) print(compose.__name__, result, fn(5))

Resultados

Y obtenemos la siguiente salida (la misma magnitud y proporción en Python 2 y 3):

manual compose 0.4963762479601428 12.25 strict_recursive_compose 0.6564744340721518 12.25 strict_recursive_compose2 0.7216697579715401 12.25 lazy_recursive_compose 1.260614730999805 12.25 iterative_compose 0.614982972969301 12.25 reduce_compose1 0.6768529079854488 12.25 reduce_compose2 0.9890829260693863 12.25

Y mis expectativas fueron confirmadas: el más rápido es, por supuesto, la composición de la función manual seguida de la implementación iterativa. La versión perezosa recursiva es mucho más lenta, probablemente debido a que cada llamada a la función crea un nuevo marco de pila y se crea una nueva tupla de funciones para cada función.

Para una comparación mejor y quizás más realista, si elimina **kwargs y cambia *args a arg en las funciones, las que las usaron tendrán más rendimiento y podemos comparar manzanas con manzanas, aquí, además de la composición manual , reduce_compose1 gana seguido por strict_recursive_compose:

manual compose 0.443808660027571 12.25 strict_recursive_compose 0.5409777010791004 12.25 strict_recursive_compose2 0.5698030130006373 12.25 lazy_recursive_compose 1.0381018499610946 12.25 iterative_compose 0.619289995986037 12.25 reduce_compose1 0.49532539502251893 12.25 reduce_compose2 0.9633988010464236 12.25

Funciones con un solo arg:

def strict_recursive_compose(*funcs): *funcs, penultimate, last = funcs if funcs: penultimate = strict_recursive_compose(*funcs, penultimate) return lambda arg: penultimate(last(arg)) def strict_recursive_compose2(*funcs): if len(funcs) > 2: penultimate = strict_recursive_compose2(*funcs[:-1]) else: penultimate = funcs[-2] return lambda arg: penultimate(funcs[-1](arg)) def lazy_recursive_compose(*funcs): def inner(arg, _funcs=funcs): if len(_funcs) > 1: return inner(_funcs[-1](arg), _funcs=_funcs[:-1]) else: return _funcs[0](arg) return inner def iterative_compose(*functions): """my implementation, only accepts one argument.""" def inner(arg): for f in reversed(functions): arg = f(arg) return arg return inner def _compose2(f, g): return lambda arg: f(g(arg)) def reduce_compose1(*fs): return reduce(_compose2, fs) def reduce_compose2(*funcs): """bug fixed - added reversed()""" return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x)

Tengo una variedad de funciones y estoy tratando de producir una función que consiste en la composición de los elementos en mi matriz. Mi enfoque es:

def compose(list): if len(list) == 1: return lambda x:list[0](x) list.reverse() final=lambda x:x for f in list: final=lambda x:f(final(x)) return final

Este método no parece estar funcionando, la ayuda será apreciada.

(Estoy invirtiendo la lista porque este es el orden de composición que quiero que tengan las funciones)


El enfoque más fácil sería primero escribir una composición de 2 funciones:

def compose2(f, g): return lambda *a, **kw: f(g(*a, **kw))

Y luego use reduce para componer más funciones:

def compose(*fs): return reduce(compose2, fs)

O puede usar alguna biblioteca , que ya contiene la función de compose .


Esta es mi version

def compose(*fargs): def inner(arg): if not arg: raise ValueError("Invalid argument") if not all([callable(f) for f in fargs]): raise TypeError("Function is not callable") return reduce(lambda arg, func: func(arg), fargs, arg) return inner

Un ejemplo de cómo se usa.

def calcMean(iterable): return sum(iterable) / len(iterable) def formatMean(mean): return round(float(mean), 2) def adder(val, value): return val + value def isEven(val): return val % 2 == 0 if __name__ == ''__main__'': # Ex1 rand_range = [random.randint(0, 10000) for x in range(0, 10000)] isRandIntEven = compose(calcMean, formatMean, partial(adder, value=0), math.floor.__call__, isEven) print(isRandIntEven(rand_range))


La implementación más confiable que he encontrado está en las toolz biblioteca de toolz . La función de compose de esta biblioteca también se ocupa de la cadena de documentos para la composición de funciones.

El código fuente está disponible gratuitamente. A continuación se muestra un ejemplo simple de uso.

from toolz import compose def f(x): return x+1 def g(x): return x*2 def h(x): return x+3 res = compose(f, g, h)(5) # 17


La respuesta de Poke es buena, pero también puede usar el paquete functional que viene con un método de composición.


No funciona porque todas las funciones anónimas que creas en el bucle se refieren a la misma variable de bucle y, por lo tanto, comparten su valor final.

Como solución rápida, puede reemplazar la asignación con:

final = lambda x, f=f, final=final: f(final(x))

O bien, puede devolver el lambda desde una función:

def wrap(accum, f): return lambda x: f(accum(x)) ... final = wrap(final, f)

Para entender lo que está pasando, prueba este experimento:

>>> l = [lambda: n for n in xrange(10)] >>> [f() for f in l] [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Este resultado sorprende a muchas personas, que esperan que el resultado sea [0, 1, 2, ...] . Sin embargo, todas las lambdas apuntan a la misma variable n , y todas se refieren a su valor final, que es 9. En su caso, todas las versiones de la final que se supone anidan terminan refiriéndose a la misma f y, peor aún, a la misma final .

El tema de las lambdas y los bucles en Python ya se ha cubierto en SO .


También puede crear una serie de funciones y usar reducir:

def f1(x): return x+1 def f2(x): return x+2 def f3(x): return x+3 x = 5 # Will print f3(f2(f1(x))) print reduce(lambda acc, x: x(acc), [f1, f2, f3], x) # As a function: def compose(*funcs): return lambda x: reduce(lambda acc, f: f(acc), funcs, x) f = compose(f1, f2, f3)


Un trazador de líneas:

compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F)

Ejemplo de uso:

f1 = lambda x: x+3 f2 = lambda x: x*2 f3 = lambda x: x-1 g = compose(f1, f2, f3) assert(g(7) == 15)


pip install funcoperators es otra biblioteca para implementarlo que permite la notación de infijo:

from funcoperators import compose # display = lambda x: hex(ord(list(x))) display = hex *compose* ord *compose* list # also works as a function display = compose(hex, ord, list)

pip install funcoperators https://pypi.org/project/funcoperators/

Descargo de responsabilidad: Soy el creador del módulo.


def compose (*functions): def inner(arg): for f in reversed(functions): arg = f(arg) return arg return inner

Ejemplo:

>>> def square (x): return x ** 2 >>> def increment (x): return x + 1 >>> def half (x): return x / 2 >>> composed = compose(square, increment, half) # square(increment(half(x))) >>> composed(5) # square(increment(half(5))) = square(increment(2.5)) = square(3.5) = 12,25 12.25