Ich bin kürzlich über diesen Code gestolpert:
function xrange($min, $max)
{
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}
Ich habe dieses yield
Schlüsselwort noch nie gesehen. Ich versuche den Code auszuführen, den ich bekomme
Parse-Fehler: Syntaxfehler, unerwarteter T_VARIABLE in Zeile x
Was ist das yield
Schlüsselwort? Ist es überhaupt gültiges PHP? Und wenn ja, wie verwende ich es?
yield
?Das Schlüsselwort yield
gibt Daten von einer Generatorfunktion zurück:
Das Herzstück einer Generatorfunktion ist das Schlüsselwort yield. In der einfachsten Form ähnelt eine yield-Anweisung einer return-Anweisung. Statt die Ausführung der Funktion zu stoppen und zurückzugeben, liefert yield einen Wert für den Code, der den Generator durchläuft, und unterbricht die Ausführung der generator-Funktion.
Eine Generatorfunktion ist effektiv eine kompaktere und effizientere Möglichkeit, einen Iterator zu schreiben. Hier können Sie eine Funktion definieren (Ihre xrange
), die berechnet und zurückgibt Werte while Sie sind drüberschleifen :
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 den $key
In foreach
steuern, indem Sie verwenden
yield $someKey => $someValue;
In der Generatorfunktion ist $someKey
Das, was Sie wollen, wobei $key
Und $someValue
Der Wert in $val
Sind. Im Beispiel der Frage ist das $i
.
Nun fragen Sie sich vielleicht, warum wir nicht einfach die native range
-Funktion von PHP verwenden, um diese Ausgabe zu erzielen. Und genau du bist. Die Ausgabe wäre die gleiche. Der Unterschied ist, wie wir dorthin gekommen sind.
Wenn wir range
PHP verwenden, werden wir es ausführen, das gesamte Array von Zahlen im Speicher erstellen und return
das gesamtes Array zum foreach
Schleife, die daraufhin die Werte ausgibt. Mit anderen Worten, die Funktion foreach
wird auf dem Array selbst ausgeführt. Die Funktion range
und die Funktion foreach
"sprechen" nur einmal. Stellen Sie sich vor, Sie bekommen ein Paket per Post. Der Zusteller gibt Ihnen das Paket und geht. Und dann packen Sie das gesamte Paket aus und nehmen heraus, was sich dort befindet.
Wenn wir die Generatorfunktion verwenden, tritt PHP in die Funktion ein und führt sie aus, bis sie entweder das Ende oder ein yield
- Schlüsselwort erfüllt. Wenn sie ein yield
gibt dann den jeweils aktuellen Wert an die äußere Schleife zurück, kehrt dann zur Generatorfunktion zurück und fährt dort fort, wo er ausgegeben wurde. Da Ihr xrange
ein for
Schleife, die ausgeführt und ausgegeben wird, bis $max
erreicht wurde. Stellen Sie sich das wie den foreach
und den Generator vor, der Ping-Pong spielt.
Offensichtlich können Generatoren verwendet werden, um Speichergrenzen zu umgehen. Abhängig von Ihrer Umgebung wird das Ausführen einer range(1, 1000000)
Ihr Skript zum Scheitern bringen, wohingegen dasselbe mit einem Generator gut funktioniert. Oder wie Wikipedia es ausdrückt:
Da Generatoren ihre Ertragswerte nur bei Bedarf berechnen, sind sie nützlich, um Sequenzen darzustellen, die teuer oder unmöglich auf einmal zu berechnen wären. Diese umfassen z.B. Unendliche Sequenzen und Live-Datenströme.
Generatoren sollen auch ziemlich schnell sein. Denken Sie jedoch daran, dass wir, wenn wir über schnell sprechen, in der Regel in sehr kleinen Zahlen sprechen. Bevor Sie jetzt loslegen und Ihren gesamten Code ändern, um Generatoren zu verwenden, sollten Sie einen Benchmark durchführen, um festzustellen, wo dies sinnvoll ist.
Ein weiterer Anwendungsfall für Generatoren sind asynchrone Coroutinen. Das Schlüsselwort yield
gibt nicht nur Werte zurück, sondern akzeptiert sie auch. Einzelheiten dazu finden Sie in den beiden unten verlinkten hervorragenden Blog-Posts.
yield
verwenden?Generatoren wurden in PHP 5.5 eingeführt. Der Versuch, yield
vor dieser Version zu verwenden, führt je nach Code, der auf das Schlüsselwort folgt, zu verschiedenen Analysefehlern. Wenn Sie also einen Analysefehler von diesem Code bekommen, aktualisieren Sie Ihr PHP.
Diese Funktion mit 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;
}
Mit nur einem Unterschied, dass a()
ein generator und b()
nur ein einfaches Array zurückgibt. Sie können auf beiden iterieren.
Außerdem weist der erste keine vollständige Anordnung zu und ist daher weniger speicherintensiv.
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#
yield
Schlü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 mit foreach einen Datensatz durchläuft, ohne ein Array im Speicher erstellen zu müssen. Dies kann dazu führen, dass Sie ein Speicherlimit überschreiten oder eine erhebliche Verarbeitungszeit für die Generierung benötigen. Stattdessen können Sie eine Generatorfunktion schreiben, die mit einer normalen Funktion identisch ist, mit der Ausnahme, dass ein Generator nicht nur einmal zurückgegeben wird, sondern auch so oft liefert, wie es erforderlich ist, um die Werte bereitzustellen, über die iteriert werden soll.
Von diesem Ort: Generatoren = Generatoren, andere Funktionen (nur einfache Funktionen) = Funktionen.
Sie sind also nützlich, wenn:
Sie müssen die Dinge einfach (oder einfach) machen;
generator ist wirklich viel einfacher als die Implementierung der Iterator-Schnittstelle. Andererseits sind Generatoren natürlich weniger funktionsfähig. vergleiche sie .
Sie müssen große Datenmengen erzeugen - Speicher sparen;
um Speicherplatz zu sparen, können wir einfach die benötigten Daten über Funktionen für jede Schleifeniteration generieren und nach der Iteration Garbage verwenden. Hier geht es also vor allem um eindeutigen 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 es im Vergleich zu Funktionen einfacher machen. überprüfen Sie 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.
sie können in einigen Fällen schneller arbeiten als sie funktionieren (siehe vorheriger Nutzen).
Mit yield
können Sie auf einfache Weise die Haltepunkte zwischen mehreren Aufgaben in einer einzigen Funktion beschreiben. Das ist alles, es gibt nichts Besonderes.
$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:
dann sind Generatoren die beste Lösung, da Sie Ihren Code nicht in viele Closures aufteilen oder mit anderem Code mischen oder Rückrufe usw. verwenden müssen. Sie verwenden einfach yield
, um einen Haltepunkt hinzuzufügen. und Sie können von diesem Haltepunkt aus fortfahren, 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!Hinweis 2: Die Verwendung von Generatoren in einer Endlosschleife ist wie das Schreiben eines Verschlusses mit unendlicher Länge. ..
Ein interessanter Aspekt, der hier diskutiert werden sollte, ist nter Bezugnahme. Jedes Mal, wenn wir einen Parameter so ändern müssen, dass er außerhalb der Funktion angezeigt 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 durch Ändern der iterierten Werte in der foreach
-Schleife die Variable $from
Im Generator geändert wird. Dies liegt daran, dass $from
Aufgrund des kaufmännischen Und-Zeichens vor dem Namen des Generators als Referenz angegeben ist. Aus diesem Grund ist die Variable $value
In der foreach
-Schleife eine Referenz auf die Variable $from
In der Generatorfunktion.