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

Sep 27 2020

Logische Operatoren in Python sind faul. Mit folgender Definition:

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

den orOperator anrufen

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

wertet nur den ersten Funktionsaufruf aus, da orerkannt wird, dass der Ausdruck Trueunabhängig vom Rückgabewert des zweiten Funktionsaufrufs ausgewertet wird . andverhält sich analog.

Wenn Sie jedoch any()(analog :) all()auf folgende Weise verwenden:

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

Alle Funktionsaufrufe werden ausgewertet, da die innere Liste zuerst erstellt wird, bevor anydie booleschen Werte ihrer Elemente durchlaufen werden. Das gleiche passiert, wenn wir die Listenkonstruktion weglassen und einfach schreiben

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

So können wir die Kraft verlieren anyist Kurzschluss , was bedeutet , dass sie, sobald das erste Element des iterable bricht , ist Truish. Wenn die Funktionsaufrufe teuer sind, ist die Bewertung aller Funktionen im Voraus ein großer Verlust und eine Verschwendung dieser Fähigkeit von any. In gewissem Sinne könnte man dies als Python-Gotcha bezeichnen, da dies für Benutzer, die versuchen, diese Funktion zu nutzen, unerwartet sein anykann und anyhäufig als eine weitere syntaktische Methode zur Verkettung einer Folge von orAnweisungen angesehen wird. Ist anyaber nur ein Kurzschluss , nicht faul , und das ist hier ein Unterschied.

anyakzeptiert eine iterable . Es sollte also eine Möglichkeit geben, einen Iterator zu erstellen, der seine Elemente nicht im Voraus bewertet, sondern sie nicht bewertet weitergibt anyund sie anynur innerhalb bewerten lässt , um eine vollständig träge Bewertung zu erzielen.

Die Frage ist also: Wie können wir anymit wirklich fauler Funktionsbewertung arbeiten? Das heißt: Wie können wir einen Iterator von Funktionsaufrufen erstellen, die anyverbraucht werden können, ohne alle Funktionsaufrufe im Voraus auszuwerten?

Antworten

7 jonathan.scholbach Sep 27 2020 at 16:28

Wir können einen Generatorausdruck verwenden , indem wir die Funktionen und ihre Argumente separat übergeben und nur im Generator wie folgt auswerten:

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

Für verschiedene Funktionen mit unterschiedlichen Signaturen könnte dies wie folgt aussehen:

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

Auf diese Weise anywird der Aufruf des next()Elements im Generator beendet, sobald ein Funktionsaufruf im Generator ausgewertet Truewird. Dies bedeutet, dass die Funktionsauswertung vollständig verzögert ist.

Ein weiterer guter Weg, um die Funktionsbewertung zu verschieben, wurde von wjandrea in einem Kommentar erwähnt: Wir können auch Lambda-Ausdrücke verwenden , wie folgt :

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