Recursive realizacja
Oto rekurencyjna realizacja, które miały jeszcze zobaczyć:
def compose(*funcs):
def inner(data, funcs=funcs):
return inner(funcs[-1](data), funcs[:-1]) if funcs else data
return inner
Nie spodziewam się, że będzie bardzo wydajnych, choć, jak to sprawia, że nowy krotka argumenty każdego wywołania rekursywnego.
Porównanie wszystkich sugestii:
Przetestujmy niektóre z tych wdrożeń i określenia, które jest najbardziej wydajnych pierwsze niektóre funkcje jeden argument (dziękuję kłuć):
def square (x):
return x ** 2
def increment (x):
return x + 1
def half (x):
return x/2
Oto nasze realizacje, podejrzewam wersja iteracyjna jest drugą najbardziej wydajną (ręczne komponowanie będzie oczywiście najszybsze).
from functools import reduce
def recursive_compose(*funcs):
def inner(data, funcs=funcs):
return inner(funcs[-1](data), funcs[:-1]) if funcs else data
return inner
def iterative_compose(*functions):
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)
I przetestować te:
import timeit
composes = (recursive_compose, iterative_compose,
reduce_compose1, reduce_compose2)
def manual():
return square(increment(half(5)))
print('manual compose', min(timeit.repeat(manual)), manual())
for compose in composes:
fn = lambda: compose(square, increment, half)(5)
result = min(timeit.repeat(fn))
print(compose.__name__, result, fn())
Wyniki
i otrzymujemy następujący wynik (tę samą wielkość i udział w Pythonie 2 i 3):
manual compose 0.607658714056015 12.25
recursive_compose 1.929560380987823 12.25
iterative_compose 1.3319460819475353 12.25
reduce_compose1 2.0850532418116927 12.25
reduce_compose2 1.5899418010376394 12.25
I moje oczekiwania zostały potwierdzone: najszybszy jest oczywiście ręczny skład funkcji, po którym następuje iteracyjne wdrożenie. Wersja rekursywna jest znacznie wolniejsza - prawdopodobnie dlatego, że każda wywołanie funkcji tworzy nową ramkę stosu i dla każdej funkcji tworzona jest nowa krotka funkcja.
Dzięki za odpowiedź, rzeczywiście zadziałało to dla mnie. Użyłem drugiej metody. Czy możesz wyjaśnić, co masz na myśli przez "końcowe zamknięcie odnoszą się do tej samej komórki", a także możesz wyjaśnić pierwszą metodę. – Starless
@Starless Zaktualizowałem odpowiedź z dłuższym wyjaśnieniem. – user4815162342