Python: Lazy Function Evaluation in any () / all ()

Sep 27 2020

Operatory logiczne w Pythonie są leniwe. Z następującą definicją:

def func(s):
    print(s)
    return True

dzwoniąc do oroperatora

>>> func('s') or func('t')
's'

ocenia tylko pierwsze wywołanie funkcji, ponieważ orrozpoznaje, że Truewynikiem wyrażenia jest wartość , niezależnie od wartości zwracanej przez drugie wywołanie funkcji. andzachowuje się analogicznie.

Jednak przy użyciu any()(analogicznie :) all()w następujący sposób:

>>> any([func('s'), func('t')])
's'
't'

wszystkie wywołania funkcji są oceniane, ponieważ lista wewnętrzna jest konstruowana jako pierwsza, przed anyrozpoczęciem iteracji po wartościach logicznych jej elementów. To samo dzieje się, gdy pomijamy konstrukcję listy i po prostu piszemy

>>> any(func('s'), func('t'))
's'
't'

W ten sposób tracimy moc anybędącego zwarcia , co oznacza, że pęknie jak tylko pierwszy element iterowalny jest truish. Jeśli wywołania funkcji są drogie, oszacowanie wszystkich funkcji z góry jest dużą stratą i stratą tej zdolności any. W pewnym sensie można by to nazwać pythonową pułapką, ponieważ może to być nieoczekiwane dla użytkowników próbujących wykorzystać tę funkcję any, a ponieważ anyczęsto jest uważane za kolejny syntaktyczny sposób łączenia sekwencji orinstrukcji. Ale anyto tylko zwarcie , a nie leniwy , i to jest różnica tutaj.

anyakceptuje iterowalne . Powinien więc istnieć sposób na stworzenie iteratora, który nie ocenia swoich elementów z góry, ale przekazuje je bez oceny anyi pozwala im oceniać anytylko wewnątrz , aby uzyskać w pełni leniwą ocenę.

Zatem pytanie brzmi: jak możemy używać anyz naprawdę leniwą oceną funkcji? To znaczy: Jak możemy wykonać iterator wywołań funkcji, które anymogą konsumować, bez uprzedniego szacowania wszystkich wywołań funkcji?

Odpowiedzi

7 jonathan.scholbach Sep 27 2020 at 16:28

Możemy użyć wyrażenia generatora , przekazując funkcje i ich argumenty osobno i oceniając tylko w generatorze:

>>> any(func(arg) for arg in ('s', 't'))
's'

W przypadku różnych funkcji z różnymi podpisami może to wyglądać następująco:

any(
    f(*args)
    for f, args in [(func1, ('s',)), (func2, (1, 't'))]
)

W ten sposób anyprzestanie wywoływać next()element w generatorze, gdy tylko jedno wywołanie funkcji w generatorze zwróci wartość True, a to oznacza, że ​​ocena funkcji jest całkowicie leniwa.

O innym fajnym sposobie na odroczenie oceny funkcji wspomniał wjandrea w komentarzu: Możemy również użyć wyrażeń lambda , na przykład:

>>> any(f() for f in [lambda: func('s'), lambda: func('t')]
's'