Python: Lazy Function Evaluation in any () / all ()
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
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'