2013-05-24 29 views
10

Mam szereg funkcji i próbuję utworzyć jedną funkcję, która składa się z składu elementów w mojej tablicy. Moje podejście jest:Komponowanie funkcji w python

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 

Ta metoda nie wydaje się działać, pomoc będzie mile widziane.

(jestem cofania listy, ponieważ jest to kolejność kompozycji Chcę funkcje być)

Odpowiedz

5

To nie działa, ponieważ wszystkie funkcje anonimowe tworzone w pętli odnoszą się do tej samej zmiennej pętli i dlatego podziel się jego ostateczną wartością.

Jak szybko naprawić, można wymienić funkcja:

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

Albo można wrócić lambda z funkcji:

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

aby zrozumieć, co się dzieje, spróbuj tego eksperymentu :

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

Ten wynik zaskakuje wielu ludzi, którzy oczekują, że wynik będzie [0, 1, 2, ...]. Jednak wszystkie lambdy wskazują na tę samą zmienną n, a wszystkie odnoszą się do jej wartości końcowej, która wynosi 9. W twoim przypadku wszystkie wersje final, które mają zostać umieszczone na końcu, odnoszą się do tego samego f, a nawet gorzej, do tego samego final.

Tematem lambd i pętli w Pythonie jest already covered on SO.

+0

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

+0

@Starless Zaktualizowałem odpowiedź z dłuższym wyjaśnieniem. – user4815162342

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

Przykład: odpowiedź

>>> 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 
2

Poke jest dobra, ale można też użyć pakietu functional, która pochodzi z metody tworzenia wiadomości.

+0

Szukałem implementacji je sam – Starless

+0

@Starless Ma wiele przepisów. Jak każdy pisarz, musisz przeczytać, aby poprawić to, co piszesz. – Marcin

+0

FWIK, functional.compose obsługuje tylko dwa argumenty. – georg

2

Można również utworzyć tablicę funkcji i używać zmniejszyć:

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) 
17

Najprostszym rozwiązaniem byłoby najpierw napisać skład 2 funkcje:

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

a następnie użyć reduce komponować więcej funkcji:

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

Lub możesz użyć some library, który już zawiera funkcję compose.

+0

Dobra odpowiedź - zawiera prosty przykład oraz odnośniki do biblioteki lib. Chciałbym także zaproponować [jaraco.functools] (http://pythonhosted.org/jaraco.functools/#jaraco.functools.compose) jako inną bibliotekę do rozważenia. –

7

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.

2

One liner:

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

Przykład użycia:

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

To jest moja wersja

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 

Przykładem jak to stosowane

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))