12901 Stimmen

Was bewirkt das Schlüsselwort "yield"?

Wozu dient die yield Schlüsselwort in Python? Was bewirkt es?

Ich versuche zum Beispiel, folgenden Code zu verstehen 1 :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Und das ist der Anrufer:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Was passiert, wenn die Methode _get_child_candidates aufgerufen wird? Wird eine Liste zurückgegeben? Ein einzelnes Element? Wird sie erneut aufgerufen? Wann werden die nachfolgenden Aufrufe beendet?


1. Dieses Stück Code wurde von Jochen Schulz (jrschulz) geschrieben, der eine großartige Python-Bibliothek für metrische Räume entwickelt hat. Dies ist der Link zum vollständigen Quellcode: <a href="https://well-adjusted.de/~jrspieker/mspace/" rel="noreferrer">Modul mspace </a>.

155voto

AbstProcDo Punkte 17041

Alles tolle Antworten, aber für Neulinge etwas schwierig.

Ich nehme an, Sie haben die return Erklärung.

Als Analogie dazu, return y yield sind Zwillinge. return bedeutet "zurückkehren und anhalten", während "nachgeben" "zurückkehren, aber weitergehen" bedeutet

  1. Versuchen Sie, eine num_list zu erhalten mit return .
def num_list(n):
    for i in range(n):
        return i

Führen Sie es aus:

In [5]: num_list(3)
Out[5]: 0

Sie sehen, dass Sie nur eine einzige Zahl erhalten und nicht eine ganze Liste. return nie erlaubt Ihnen, glücklich zu herrschen, nur einmal implementiert und beenden.

  1. Da kommt yield

Ersetzen Sie return mit yield :

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Jetzt gewinnst du, um alle Zahlen zu bekommen.

Im Vergleich zu return der einmal läuft und dann aufhört, yield läuft mal du gehobelt. Sie können interpretieren return als return one of them et yield als return all of them . Dies wird als iterable .

  1. Einen weiteren Schritt können wir umschreiben yield Anweisung mit return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

Es ist der Kern über yield .

Der Unterschied zwischen einer Liste return Ausgänge und das Objekt yield Ausgabe ist:

Sie werden immer [0, 1, 2] von einem Listenobjekt erhalten, aber Sie können sie nur von "dem Objekt" abrufen yield Ausgabe' einmal. Sie hat also einen neuen Namen generator Objekt, wie es in Out[11]: <generator object num_list at 0x10327c990> .

Zum Schluss noch eine Metapher, um es zu begreifen:

  • return y yield sind Zwillinge
  • list y generator sind Zwillinge

143voto

alinsoar Punkte 14685

Aus programmtechnischer Sicht sind die Iteratoren implementiert als donnert .

Um Iteratoren, Generatoren und Thread-Pools für gleichzeitige Ausführung usw. als Thunks zu implementieren, verwendet man Nachrichten, die an ein Abschlussobjekt gesendet werden der einen Dispatcher hat, und die Dispatcher antwortet auf "Nachrichten" .

" nächste " ist eine Nachricht, die an eine Schließung gesendet wird, die mit dem " iter Anruf".

Es gibt viele Möglichkeiten, diese Berechnung durchzuführen. Ich habe Mutation verwendet, aber es ist auch möglich, diese Art von Berechnung ohne Mutation durchzuführen, indem man den aktuellen Wert und den nächsten Yielder zurückgibt (wodurch es referentiell transparent ). Racket verwendet eine Reihe von Transformationen des Ausgangsprogramms in einige Zwischensprachen, wobei eine dieser Umschreibungen dazu führt, dass der Ertragsoperator in eine Sprache mit einfacheren Operatoren umgewandelt wird.

Hier ist eine Demonstration, wie yield umgeschrieben werden könnte, die die Struktur von R6RS verwendet, aber die Semantik ist identisch mit der von Python. Es handelt sich um dasselbe Berechnungsmodell, und es ist nur eine Änderung der Syntax erforderlich, um es mit yield von Python umzuschreiben.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->

134voto

Dustin Getz Punkte 20462

Hier sind einige Python-Beispiele, die zeigen, wie man Generatoren tatsächlich implementiert, als ob Python keinen syntaktischen Zucker für sie bereitstellen würde:

Als Python-Generator:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Verwendung lexikalischer Schließungen anstelle von Generatoren

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Verwendung von Objektabschlüssen anstelle von Generatoren (weil ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)

123voto

johnzachary Punkte 2365

Ich wollte eigentlich schreiben: "Lesen Sie Seite 19 von Beazleys 'Python: Essential Reference' für eine schnelle Beschreibung von Generatoren", aber so viele andere haben bereits gute Beschreibungen gepostet.

Außerdem ist zu beachten, dass yield können in Koroutinen als das Dual ihrer Verwendung in Generatorfunktionen verwendet werden. Allerdings ist dies nicht die gleiche Verwendung wie in Ihrem Codeschnipsel, (yield) kann als Ausdruck in einer Funktion verwendet werden. Wenn ein Aufrufer einen Wert an die Methode sendet, indem er die send() Methode, dann wird die Koroutine bis zur nächsten (yield) Anweisung angetroffen wird.

Generatoren und Koroutinen sind eine gute Möglichkeit, Anwendungen mit Datenfluss einzurichten. Ich dachte, es wäre interessant, etwas über die andere Verwendung der yield Anweisung in Funktionen.

105voto

Engin OZTURK Punkte 2015

Hier ist ein einfaches Beispiel:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Ausgabe:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Ich bin kein Python-Entwickler, aber es sieht für mich so aus yield hält die Position des Programmablaufs fest und die nächste Schleife beginnt an der Position "yield". Es scheint, wie es an dieser Position wartet, und kurz vor, dass die Rückkehr einen Wert außerhalb, und das nächste Mal weiter zu arbeiten.

Es scheint eine interessante und schöne Fähigkeit zu sein :D

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X