web-dev-qa-db-fra.com

Que signifie rendement en PHP?

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?

204
Gordon

Qu'est-ce que yield?

Le mot clé yieldrenvoie 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.

Qu'est-ce qu'une fonction génératrice?

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.

Quelle est la différence avec les fonctions normales?

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.

Pourquoi ai-je besoin de ça?

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.

Depuis quand puis-je utiliser 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.

Sources et lectures complémentaires:

301
Gordon

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.

27
tsusanka

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#
20
Think Big

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);

20
QArea

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:

  • mémoire libre entre les lignes de traitement de la base de données
  • exécuter d'autres tâches qui fournissent une dépendance à la tâche suivante, mais qui ne sont pas liées par la compréhension du code actuel
  • faire des appels asynchrones et attendre les résultats
  • etc ...

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. ..

7
inf3rno

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.

1
bodi0