Je suis récemment tombé sur ce code:
function xrange($min, $max)
{
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}
Je n'ai jamais vu ce mot clé yield
auparavant. Essayer d'exécuter le code que je reçois
Erreur d'analyse: erreur de syntaxe, T_VARIABLE inattendu sur la ligne x
Alors, quel est ce mot-clé yield
? Est-ce même valide PHP? Et si c'est le cas, comment puis-je l'utiliser?
yield
?Le mot clé yield
renvoie les données d'une fonction génératrice:
Le cœur d'une fonction génératrice est le mot clé rendement. Dans sa forme la plus simple, une instruction de rendement ressemble beaucoup à une instruction de retour, à la différence qu'au lieu d'arrêter l'exécution de la fonction et de la renvoyer, rendement donne plutôt une valeur au code en boucle sur le générateur et met en pause son exécution.
Une fonction de générateur est effectivement un moyen plus compact et efficace d’écrire un Iterator . Il vous permet de définir une fonction (votre xrange
) qui va calculer et renvoyer les valeurs tant que vous êtes - en boucle :
foreach (xrange(1, 10) as $key => $value) {
echo "$key => $value", PHP_EOL;
}
Cela créerait la sortie suivante:
0 => 1
1 => 2
…
9 => 10
Vous pouvez également contrôler le $key
dans le foreach
en utilisant
yield $someKey => $someValue;
Dans la fonction de générateur, $someKey
est ce que vous souhaitez voir apparaître pour $key
et $someValue
étant la valeur dans $val
. Dans l'exemple de la question, il s'agit de $i
.
Vous pouvez maintenant vous demander pourquoi nous n'utilisons pas simplement la fonction native range
de PHP de PHP pour obtenir ce résultat. Et vous avez raison. La sortie serait la même. La différence est comment nous sommes arrivés là.
Lorsque nous utilisons range
PHP, nous l'exécuterons, créerons tout le tableau de nombres en mémoire et return
que tout le tableau à la foreach
boucle qui va ensuite passer dessus et sortir les valeurs. En d'autres termes, la foreach
fonctionnera sur le tableau lui-même. La fonction range
et la foreach
ne "parlent" qu'une seule fois. Pensez-y comme si vous receviez un colis par la poste. Le livreur vous remettra le colis et partira. Et ensuite, vous déballez le paquet en entier, en retirant tout ce qu’il contient.
Lorsque nous utilisons la fonction générateur, PHP entrera dans la fonction et l'exécutera jusqu'à ce qu'elle atteigne la fin ou un mot clé yield
. Quand il rencontre un yield
, il retournera alors quelle que soit la valeur à ce moment-là à la boucle externe. Ensuite, il retourne à la fonction de générateur et continue là où il a cédé. Étant donné que votre xrange
détient une boucle for
, elle sera exécutée et restituée jusqu'à ce que $max
soit atteint. Pensez-y comme à la foreach
et au générateur qui joue au ping-pong.
De toute évidence, les générateurs peuvent être utilisés pour contourner les limites de la mémoire. En fonction de votre environnement, une range(1, 1000000)
fatale votre script alors que la même chose avec un générateur fonctionnera parfaitement. Ou comme le dit Wikipedia:
Comme les générateurs calculent leurs valeurs de rendement uniquement à la demande, ils sont utiles pour représenter des séquences qui seraient coûteuses ou impossibles à calculer en une fois. Ceux-ci incluent par exemple séquences infinies et flux de données en direct.
Les générateurs sont également censés être assez rapides. Mais gardez à l'esprit que quand on parle de rapidité, on parle généralement en très petit nombre. Donc, avant de vous lancer et de changer tout votre code pour qu'il utilise des générateurs, faites un test de performance pour voir où cela a un sens.
Les coroutines asynchrones constituent un autre cas d'utilisation des générateurs. Le mot clé yield
ne renvoie pas seulement des valeurs, il les accepte également. Pour plus de détails à ce sujet, voir les deux excellents articles de blog liés ci-dessous.
yield
?Des générateurs ont été introduits dans PHP 5.5 . Essayer d'utiliser yield
avant cette version entraînera diverses erreurs d'analyse, en fonction du code qui suit le mot-clé. Donc, si vous obtenez une erreur d'analyse de ce code, mettez à jour votre PHP.
Cette fonction utilisant le rendement:
function a($items) {
foreach ($items as $item) {
yield $item + 1;
}
}
est presque le même que celui-ci sans:
function b($items) {
$result = [];
foreach ($items as $item) {
$result[] = $item + 1;
}
return $result;
}
Avec une seule différence, a()
renvoie un générateur et b()
un simple tableau. Vous pouvez parcourir les deux.
En outre, le premier n'alloue pas un tableau complet et nécessite donc moins de mémoire.
exemple simple
<?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#';
?>
sortie
#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
Le mot clé yield
sert à définir les "générateurs" dans PHP 5.5. Ok, alors qu'est-ce qu'un générateur ?
De php.net:
Les générateurs constituent un moyen simple d'implémenter des itérateurs simples sans la charge ou la complexité d'implémenter une classe implémentant l'interface Iterator.
Un générateur vous permet d'écrire du code qui utilise foreach pour effectuer une itération sur un ensemble de données sans avoir à construire un tableau en mémoire, ce qui peut vous amener à dépasser une limite de mémoire ou à générer un temps de traitement considérable. Au lieu de cela, vous pouvez écrire une fonction de générateur, identique à une fonction normale, à la différence qu'au lieu de renvoyer une fois, un générateur peut générer autant de fois que nécessaire pour fournir les valeurs à itérer.
À partir de cet endroit: générateurs = générateurs, autres fonctions (seulement des fonctions simples) = fonctions.
Donc, ils sont utiles quand:
vous devez faire des choses simples (ou simples);
le générateur est vraiment beaucoup plus simple que d’implémenter l’interface Iterator. D'autre part, bien entendu, les générateurs sont moins fonctionnels. comparez-les .
vous devez générer de grandes quantités de données pour économiser de la mémoire;
en fait, pour économiser de la mémoire, nous pouvons simplement générer les données nécessaires via des fonctions pour chaque itération de boucle, et après itération, utiliser garbage. alors voici les points principaux: code clair et probablement performances. voyez ce qui est mieux pour vos besoins.
vous devez générer une séquence, qui dépend de valeurs intermédiaires;
c'est l'extension de la pensée précédente. les générateurs peuvent faciliter les choses par rapport aux fonctions. Vérifiez exemple de Fibonacci , et essayez de créer une séquence sans générateur. Les générateurs peuvent également fonctionner plus rapidement dans ce cas, du moins en raison du stockage de valeurs intermédiaires dans des variables locales;
vous devez améliorer les performances.
ils peuvent travailler plus vite que les fonctions dans certains cas (voir avantage précédent);
Avec yield
, vous pouvez facilement décrire les points d'arrêt entre plusieurs tâches dans une même fonction. C'est tout, il n'y a rien de spécial à ce sujet.
$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, ...);
Si tâche1 et tâche2 sont étroitement liés, mais que vous avez besoin d'un point d'arrêt entre eux pour effectuer autre chose:
alors les générateurs sont la meilleure solution, car vous n'avez pas à diviser votre code en plusieurs fermetures, ni le mélanger avec un autre code, ni utiliser des rappels, etc. Vous utilisez simplement yield
pour ajouter un point d'arrêt, et vous pouvez continuer à partir de ce point d'arrêt si vous êtes prêt.
Ajouter un point d'arrêt sans générateurs:
$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);
//...
Ajouter un point d'arrêt avec des générateurs
$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);
note: il est facile de se tromper avec les générateurs, écrivez donc toujours des tests unitaires avant de les implémenter!note2: Utiliser des générateurs dans une boucle infinie revient à écrire une fermeture de longueur infinie. ..
Un aspect intéressant, qui mérite d’être discuté ici, est donnant par référence. Chaque fois que nous devons modifier un paramètre de sorte qu'il se reflète en dehors de la fonction, nous devons le transmettre par référence. Pour appliquer cela aux générateurs, nous ajoutons simplement une esperluette &
au nom du générateur et à la variable utilisée dans l'itération:
<?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...
L'exemple ci-dessus montre comment la modification des valeurs itérées dans la boucle foreach
modifie la variable $from
dans le générateur. Cela est dû au fait que $from
est renvoyé par référence en raison de l'esperluette avant le nom du générateur. Pour cette raison, la variable $value
dans la boucle foreach
fait référence à la variable $from
dans la fonction de générateur.