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