Rendite und Rendite in derselben Funktion

75

Was genau passiert, wenn Yield und Return in Python wie folgt in derselben Funktion verwendet werden?

def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1: return
        yield start
        start += len(sub) # use start += 1 to find overlapping matches

Ist es noch ein Generator?

nekomimi
quelle

Antworten:

74

Ja, es ist immer noch ein Generator. Das returnist (fast) gleichbedeutend mit Erhöhen StopIteration.

PEP 255 beschreibt es:

Spezifikation: Rückgabe

Eine Generatorfunktion kann auch return-Anweisungen der folgenden Form enthalten:

"return"

Beachten Sie, dass eine Ausdrucksliste für return-Anweisungen im Hauptteil eines Generators nicht zulässig ist (obwohl sie natürlich in den Hauptteilen von Nicht-Generator-Funktionen erscheinen können, die im Generator verschachtelt sind).

Wenn eine return-Anweisung gefunden wird, fährt die Steuerung wie bei jeder Funktionsrückgabe fort und führt die entsprechenden finally-Klauseln aus (falls vorhanden). Dann wird eine StopIteration-Ausnahme ausgelöst, die signalisiert, dass der Iterator erschöpft ist. Eine StopIteration-Ausnahme wird auch ausgelöst, wenn die Steuerung ohne explizite Rückgabe vom Ende des Generators ausgeht.

Beachten Sie, dass return sowohl für Generatorfunktionen als auch für Nicht-Generatorfunktionen "Ich bin fertig und habe nichts Interessantes zurückzugeben" bedeutet.

Beachten Sie, dass return nicht immer gleichbedeutend mit dem Auslösen von StopIteration ist: Der Unterschied besteht darin, wie einschließende try / Except-Konstrukte behandelt werden. Zum Beispiel,

>>> def f1():
...     try:
...         return
...     except:
...        yield 1
>>> print list(f1())
[]

weil, wie in jeder Funktion, return einfach beendet wird, aber

>>> def f2():
...     try:
...         raise StopIteration
...     except:
...         yield 42
>>> print list(f2())
[42]

weil StopIteration wie jede Ausnahme von einem bloßen "außer" erfasst wird.

NPE
quelle
3
Wissen Sie, was passieren würde, wenn der returneinen Streit hätte?
zwol
15
@Zack In Python 2.x wäre es ein SyntaxError : SyntaxError: 'return' with argument inside generator. Es ist in Python 3.x zulässig, soll jedoch hauptsächlich mit Coroutinen verwendet werden. Sie führen asynchrone Aufrufe anderer Coroutinen mit yield coroutine()(oder yield from coroutine(), abhängig vom verwendeten asynchronen Framework) durch und geben alles zurück, was Sie von der Coroutine zurückgeben möchten mit return value. In Python 2.x müssen Sie einen Trick verwenden raise Return(value), um Werte von Coroutinen zurückzugeben.
Dano
30

Ja, es ist immer noch ein Generator. Ein leeres returnoder return Nonekann verwendet werden, um eine Generatorfunktion zu beenden. Dies entspricht dem Erhöhen von a StopIteration( Einzelheiten finden Sie in der Antwort von @ NPE ).

Beachten Sie, dass eine Rückgabe mit Nicht-Keine-Argumenten SyntaxErrorin Python-Versionen vor 3.3 erfolgt.

Wie @BrenBarn in Kommentaren ab Python 3.3 hervorhob, wird der Rückgabewert jetzt an übergeben StopIteration.

Aus PEP 380 :

In einem Generator die Anweisung

return value

ist semantisch äquivalent zu

raise StopIteration(value)
Ashwini Chaudhary
quelle
1
Wissen Sie, was passieren würde, wenn der returnein Argument hätte (außer None)?
zwol
6
In Python 3.3 und returnhöher können Sie das Argument mit einem Argument an die angehobene StopIteration übergeben. Siehe diese Frage .
BrenBarn
@BrenBarn Interessant, wusste das nicht.
Ashwini Chaudhary
1
@AshwiniChaudhary Die Coroutine-Implementierung im neuen asyncioModul basiert auf dieser Funktion (zusammen mit dem yield fromSchlüsselwort).
Dano
3
@AshwiniChaudhary Dies ermöglichte grundlegende Coroutinen in Python - die Fähigkeit, Werte / Ausnahmen an Generatoren zu senden und über diese zu empfangen value = yieldusw. Die Einführung von yield fromund die Fähigkeit, returnWerte von Generatoren zu erhalten, kam mit PEP 380 , die beide von genutzt werden asyncio. Sie können immer noch eine robuste Coroutine-Implementierung mit nur den Funktionen von PEP 343 haben. Das Schreiben ist nur ein wenig weniger sauber.
Dano
11

Es gibt eine Möglichkeit, eine Yield- und Return-Methode in einer Funktion zu erreichen, mit der Sie einen Wert oder Generator zurückgeben können.

Es ist wahrscheinlich nicht so sauber, wie Sie es möchten, aber es macht das, was Sie erwarten.

Hier ist ein Beispiel:

def six(how_many=None):
    if how_many is None or how_many < 1:
        return None  # returns value

    if how_many == 1:
        return 6  # returns value

    def iter_func():
        for count in range(how_many):
            yield 6
    return iter_func()  # returns generator
William Rusnack
quelle
nicht klar, "dass es tut, was Sie erwarten". Können Sie ein Beispiel dafür geben, wie Ihr Ansatz sinnvoll eingesetzt werden kann?
ShpielMeister
"dass es tut, was Sie erwarten" wie im Thema der Frage "Rendite und Rendite in derselben Funktion". Persönlich habe ich zu Haskell gewechselt und dies könnte mit einem algebraischen Datentyp gut verwendet / verwaltet werden, aber mit Python ist es schwierig genug, Ihre Typen zu verwalten, und dies passt nicht gut in die Python-Typdeklarationen. Wenn Sie also die Frage stellen, wie dies sinnvoll genutzt werden kann, verwenden Sie es bitte nicht. Andernfalls können Sie auf diese Weise no, einen einzelnen oder mehrere Werte zurückgeben. Dies könnte verwendet werden, um einen Baum effektiv zu durchqueren.
William Rusnack
0

Hinweis: StopIterationMit dem folgenden Beispiel erhalten Sie keine Ausnahme.

def odd(max):
    n = 0
    while n < max:
        yield n
        n = n + 1
    return 'done'


for x in odd(3):
    print(x)

Die forSchleife fängt es auf. Das ist das Signal zum Stoppen

Aber Sie können es auf diese Weise fangen:

g = odd(3)

while True:
    try:
        x = next(g)
        print(x)
    except StopIteration as e:
        print("g return value:", e.value)
        break
Rick
quelle