Was bedeutet Ausbeute in PHP?

232

Ich bin kürzlich über diesen Code gestolpert:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

Ich habe dieses yieldSchlüsselwort noch nie gesehen . Ich versuche den Code auszuführen, den ich bekomme

Analysefehler: Syntaxfehler, unerwartetes T_VARIABLE in Zeile x

Was ist dieses yieldSchlüsselwort? Ist es überhaupt gültiges PHP? Und wenn ja, wie verwende ich es?

Gordon
quelle

Antworten:

355

Was ist yield?

Das yieldSchlüsselwort gibt Daten von einer Generatorfunktion zurück:

Das Herzstück einer Generatorfunktion ist das Yield-Schlüsselwort. In seiner einfachsten Form ähnelt eine Yield-Anweisung einer Return-Anweisung, mit der Ausnahme, dass Yield nicht die Ausführung der Funktion stoppt und zurückgibt, sondern stattdessen einen Wert für die Code-Schleife über den Generator bereitstellt und die Ausführung der Generatorfunktion anhält.

Was ist eine Generatorfunktion?

Eine Generatorfunktion ist effektiv eine kompaktere und effizientere Möglichkeit, einen Iterator zu schreiben . Sie können eine Funktion (Ihre xrange) definieren, die Werte berechnet und zurückgibt , während Sie sie durchlaufen :

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}

Dies würde die folgende Ausgabe erzeugen:

0 => 1
1 => 2

9 => 10

Sie können auch die Kontrolle $keyin dem foreachdurch den Einsatz

yield $someKey => $someValue;

In der Generatorfunktion, $someKeyist alles , was Sie erscheinen möchten $keyund $someValuein dem Wert sein $val. Im Beispiel der Frage ist das$i .

Was ist der Unterschied zu normalen Funktionen?

Jetzt fragen Sie sich vielleicht, warum wir nicht einfach die native Funktion von PHP rangeverwenden , um diese Ausgabe zu erzielen. Und richtig bist du. Die Ausgabe wäre die gleiche. Der Unterschied ist, wie wir dorthin gekommen sind.

Wenn wir rangePHP verwenden, wird es ausgeführt, das gesamte Array von Zahlen im Speicher und returndas gesamte Array in der foreachSchleife erstellt, die dann darüber hinweggeht und die Werte ausgibt. Mit anderen Worten, das foreachwird auf dem Array selbst arbeiten. Die rangeFunktion und das foreacheinzige "Gespräch" einmal. Stellen Sie sich vor, Sie bekommen ein Paket per Post. Der Zusteller wird Ihnen das Paket geben und gehen. Und dann packen Sie das gesamte Paket aus und nehmen alles heraus, was sich darin befindet.

Wenn wir die Generatorfunktion verwenden, tritt PHP in die Funktion ein und führt sie aus, bis sie entweder das Ende oder ein yieldSchlüsselwort erreicht. Wenn es auf a trifft yield, gibt es den zu diesem Zeitpunkt angegebenen Wert an die äußere Schleife zurück. Dann geht es zurück in die Generatorfunktion und setzt dort fort, wo es nachgegeben hat. Da Sie xrangeeine forSchleife halten, wird diese ausgeführt und gibt nach, bis sie $maxerreicht wurde. Stellen Sie sich das wie foreachden Generator vor, der Tischtennis spielt.

Warum brauche ich das?

Offensichtlich können Generatoren verwendet werden, um Speichergrenzen zu umgehen. Abhängig von Ihrer Umgebung kann das Ausführen eines range(1, 1000000)Skripts Ihr Skript beschädigen, während das gleiche mit einem Generator einwandfrei funktioniert. Oder wie Wikipedia es ausdrückt:

Da Generatoren ihre Ertragswerte nur bei Bedarf berechnen, sind sie nützlich, um Sequenzen darzustellen, deren Berechnung teuer oder unmöglich wäre. Dazu gehören zB unendliche Sequenzen und Live-Datenströme.

Generatoren sollen auch ziemlich schnell sein. Aber denken Sie daran, dass wir, wenn wir schnell sprechen, normalerweise in sehr geringer Anzahl sprechen. Bevor Sie jetzt loslegen und Ihren gesamten Code ändern, um Generatoren zu verwenden, führen Sie einen Benchmark durch, um festzustellen, wo dies sinnvoll ist.

Ein weiterer Anwendungsfall für Generatoren sind asynchrone Coroutinen. Das yieldSchlüsselwort gibt nicht nur Werte zurück, sondern akzeptiert sie auch. Einzelheiten dazu finden Sie in den beiden unten verlinkten ausgezeichneten Blog-Posts.

Seit wann kann ich verwenden yield?

Generatoren wurden in PHP 5.5 eingeführt . Der Versuch, yieldvor dieser Version zu verwenden , führt je nach dem Code, der auf das Schlüsselwort folgt, zu verschiedenen Analysefehlern. Wenn Sie also einen Analysefehler von diesem Code erhalten, aktualisieren Sie Ihr PHP.

Quellen und weiterführende Literatur:

Gordon
quelle
1
Bitte erläutern Sie, welche Vorteile yeildeine Lösung wie diese bietet
Mike
1
Ah, nun, und die Mitteilungen, die ich generierte. Huh. Nun, ich habe versucht, Generatoren für PHP> = 5.0.0 mit einer Helferklasse zu emulieren, und ja, etwas weniger lesbar, aber ich kann dies in Zukunft verwenden. Interessantes Thema. Vielen Dank!
Mike
Nicht Lesbarkeit, sondern Speichernutzung! Vergleichen Sie den verwendeten Speicher für die Iteration über return range(1,100000000)und for ($i=0; $i<100000000; $i++) yield $i
emix
@mike ja, das ist aber schon in meiner Antwort erklärt. Im Beispiel des anderen Mike ist der Speicher kaum ein Problem, da er nur 10 Werte iteriert.
Gordon
1
@Mike Ein Problem mit dem xrange besteht darin, dass die Verwendung von statischen Grenzwerten beispielsweise zum Verschachteln nützlich ist (z. B. zum Suchen über eine n-dimensionale Mannigfaltigkeit oder zum Beispiel über eine rekursive Quicksortierung mit Generatoren). Sie können xrange-Schleifen nicht verschachteln, da es nur eine einzige Instanz ihres Zählers gibt. Die Yield-Version leidet nicht unter diesem Problem.
Shayne
43

Diese Funktion verwendet Ausbeute:

function a($items) {
    foreach ($items as $item) {
        yield $item + 1;
    }
}

ist fast das gleiche wie dieses ohne:

function b($items) {
    $result = [];
    foreach ($items as $item) {
        $result[] = $item + 1;
    }
    return $result;
}

Der einzige Unterschied besteht darin, dass a()ein Generator und b()nur ein einfaches Array zurückgegeben werden. Sie können auf beiden iterieren.

Außerdem weist der erste kein vollständiges Array zu und ist daher weniger speicherintensiv.

Tsusanka
quelle
2
Anmerkungen aus den offiziellen Dokumenten hinzufügen: In PHP 5 konnte ein Generator keinen Wert zurückgeben. Dies würde zu einem Kompilierungsfehler führen. Eine leere return-Anweisung war eine gültige Syntax innerhalb eines Generators und würde den Generator beenden. Seit PHP 7.0 kann ein Generator Werte zurückgeben, die mit Generator :: getReturn () abgerufen werden können. php.net/manual/en/language.generators.syntax.php
Programmierer Dancuk
Einfach und prägnant.
John Miller
24

einfaches Beispiel

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

Ausgabe

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

fortgeschrittenes Beispiel

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

Ausgabe

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#
Groß denken
quelle
Also kehrt es zurück, ohne die Funktion zu unterbrechen?
Lucas Bustamante
22

yieldSchlüsselwort dient zur Definition von "Generatoren" in PHP 5.5. Ok, was ist dann ein Generator ?

Von php.net:

Generatoren bieten eine einfache Möglichkeit, einfache Iteratoren zu implementieren, ohne den Aufwand oder die Komplexität der Implementierung einer Klasse, die die Iterator-Schnittstelle implementiert.

Mit einem Generator können Sie Code schreiben, der foreach verwendet, um über einen Datensatz zu iterieren, ohne ein Array im Speicher erstellen zu müssen. Dies kann dazu führen, dass Sie ein Speicherlimit überschreiten oder eine beträchtliche Verarbeitungszeit zum Generieren benötigen. Stattdessen können Sie eine Generatorfunktion schreiben, die mit einer normalen Funktion identisch ist, mit der Ausnahme, dass ein Generator anstelle einer einmaligen Rückgabe so oft nachgeben kann, wie es erforderlich ist, um die zu iterierenden Werte bereitzustellen.

Von diesem Ort aus: Generatoren = Generatoren, andere Funktionen (nur einfache Funktionen) = Funktionen.

Sie sind also nützlich, wenn:

  • Sie müssen einfache (oder einfache) Dinge tun.

    Generator ist wirklich viel einfacher als die Implementierung der Iterator-Schnittstelle. Andererseits sind Generatoren natürlich weniger funktionsfähig. vergleiche sie .

  • Sie müssen GROSSE Datenmengen generieren, um Speicherplatz zu sparen.

    Um Speicherplatz zu sparen, können wir einfach die erforderlichen Daten über Funktionen für jede Schleifeniteration generieren und nach der Iteration Müll verwenden. Hier geht es also hauptsächlich um klaren Code und wahrscheinlich um Leistung. Sehen Sie, was für Ihre Bedürfnisse besser ist.

  • Sie müssen eine Sequenz generieren, die von Zwischenwerten abhängt.

    Dies erweitert den vorherigen Gedanken. Generatoren können die Arbeit im Vergleich zu Funktionen erleichtern. Überprüfen Sie das Fibonacci-Beispiel und versuchen Sie, eine Sequenz ohne Generator zu erstellen. Auch Generatoren können in diesem Fall schneller arbeiten, zumindest weil Zwischenwerte in lokalen Variablen gespeichert werden.

  • Sie müssen die Leistung verbessern.

    In einigen Fällen können sie schneller arbeiten als Funktionen (siehe vorherigen Vorteil).

QArea
quelle
1
Ich habe nicht verstanden, wie Generatoren funktionieren. Diese Klasse implementiert die Iteratorschnittstelle. Soweit ich weiß, können ich mit den Iterator-Klassen konfigurieren, wie ich über ein Objekt iterieren möchte. Zum Beispiel erhält der ArrayIterator ein Array oder Objekt, damit ich Werte und Schlüssel während der Iteration ändern kann. Wenn Iteratoren also das gesamte Objekt / Array erhalten, wie muss der Generator dann nicht das gesamte Array im Speicher erstellen?
user3021621
7

Mit können yieldSie einfach die Haltepunkte zwischen mehreren Aufgaben in einer einzigen Funktion beschreiben. Das ist alles, es ist nichts Besonderes daran.

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);

Wenn task1 und task2 eng miteinander verbunden sind, Sie jedoch einen Haltepunkt zwischen ihnen benötigen, um etwas anderes zu tun:

  • freier Speicher zwischen der Verarbeitung von Datenbankzeilen
  • Führen Sie andere Aufgaben aus, die eine Abhängigkeit von der nächsten Aufgabe bieten, die jedoch nicht mit dem Verständnis des aktuellen Codes zusammenhängen
  • Führen Sie asynchrone Anrufe durch und warten Sie auf die Ergebnisse
  • und so weiter ...

Dann sind Generatoren die beste Lösung, da Sie Ihren Code nicht in viele Abschlüsse aufteilen oder mit anderem Code mischen oder Rückrufe usw. verwenden müssen. Sie müssen nur yieldeinen Haltepunkt hinzufügen, und Sie können damit fortfahren Haltepunkt, wenn Sie bereit sind.

Haltepunkt ohne Generatoren hinzufügen:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

Haltepunkt mit Generatoren hinzufügen

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

Hinweis: Es ist leicht, Fehler mit Generatoren zu machen. Schreiben Sie daher immer Unit-Tests, bevor Sie sie implementieren! note2: Die Verwendung von Generatoren in einer Endlosschleife ist wie das Schreiben eines Verschlusses mit unendlicher Länge ...

inf3rno
quelle
4

Keine der obigen Antworten zeigt ein konkretes Beispiel für die Verwendung massiver Arrays, die von nicht numerischen Elementen gefüllt werden. Hier ist ein Beispiel mit einem Array, das von explode()einer großen TXT-Datei generiert wurde (262 MB in meinem Anwendungsfall):

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Die Ausgabe war:

Starting memory usage: 415160
Final memory usage: 270948256

Vergleichen Sie das nun mit einem ähnlichen Skript unter Verwendung des yieldSchlüsselworts:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Die Ausgabe für dieses Skript war:

Starting memory usage: 415152
Final memory usage: 415616

Die Einsparungen bei der Speichernutzung waren eindeutig beträchtlich (ΔMemoryUsage -----> ~ 270,5 MB im ersten Beispiel, ~ 450B im zweiten Beispiel).

David Partyka
quelle
3

Ein interessanter Aspekt, der hier diskutiert werden sollte, ist die Bezugnahme . Jedes Mal, wenn wir einen Parameter so ändern müssen, dass er außerhalb der Funktion wiedergegeben wird, müssen wir diesen Parameter als Referenz übergeben. Um dies auf Generatoren anzuwenden, stellen wir &dem Namen des Generators und der in der Iteration verwendeten Variablen einfach ein kaufmännisches Und voran:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

Das obige Beispiel zeigt, wie das Ändern der iterierten Werte innerhalb der foreachSchleife die $fromVariable innerhalb des Generators ändert . Dies liegt daran , $fromwird durch Bezugnahme ergibt aufgrund der Et - Zeichen vor dem Generator Namen. Aus diesem Grund ist die $valueVariable innerhalb der foreachSchleife eine Referenz auf die $fromVariable innerhalb der Generatorfunktion.

Bud Damyanov
quelle
0

Der folgende Code zeigt, wie die Verwendung eines Generators ein Ergebnis vor Abschluss zurückgibt, im Gegensatz zum herkömmlichen Ansatz ohne Generator, bei dem nach vollständiger Iteration ein vollständiges Array zurückgegeben wird. Mit dem folgenden Generator werden die Werte zurückgegeben, wenn sie bereit sind. Sie müssen nicht warten, bis ein Array vollständig gefüllt ist:

<?php 

function sleepiterate($length) {
    for ($i=0; $i < $length; $i++) {
        sleep(2);
        yield $i;
    }
}

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}
Risteard
quelle
Es ist also nicht möglich, Yield für die Generierung von HTML-Code in PHP zu verwenden? Ich kenne die Vorteile in einer realen Umgebung nicht
Giuseppe Lodi Rizzini
@ GiuseppeLodiRizzini was bringt dich dazu, das zu denken?
Brad Kent