mirror of
https://github.com/macintoshplus/doc-fr.git
synced 2026-03-24 08:52:09 +01:00
616 lines
16 KiB
XML
616 lines
16 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: 31d42a7a6e9ecf4227f6b1dc6936eccd40846fa7 Maintainer: yannick Status: ready -->
|
|
<!-- Reviewed: no -->
|
|
<!-- CREDITS: DAnnebicque -->
|
|
|
|
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
|
|
<title>Generators</title>
|
|
|
|
<sect1 xml:id="language.generators.overview">
|
|
<title>Résumé sur les générateurs</title>
|
|
<?phpdoc print-version-for="generators"?>
|
|
|
|
<para>
|
|
Les générateurs fournissent une façon simple de mettre en place des
|
|
<link linkend="language.oop5.iterations">itérateurs</link>
|
|
sans le coût ni la complexité du développement d'une classe qui implémente
|
|
l'interface <classname>Iterator</classname>.
|
|
</para>
|
|
|
|
<para>
|
|
Un générateur vous permet d'écrire du code qui utilise &foreach;
|
|
pour parcourir un jeu de données, sans avoir à construire un tableau
|
|
en mémoire pouvant conduire à dépasser la limite de la mémoire ou nécessiter
|
|
un temps important pour sa génération. Au lieu de cela, vous pouvez écrire une fonction
|
|
générateur, qui est identique à une
|
|
<link linkend="functions.user-defined">fonction</link> normale,
|
|
mis à part le fait qu'au lieu de <link linkend="functions.returning-values">retourner</link>
|
|
une seule fois, un générateur peut utiliser &yield; autant de fois que nécessaire, afin de
|
|
fournir les valeurs à parcourir.
|
|
</para>
|
|
|
|
<para>
|
|
Un exemple simple de ce mécanisme est la ré-implémentation
|
|
de la fonction <function>range</function> sous la forme d'un générateur.
|
|
La fonction standard <function>range</function> doit générer un tableau
|
|
contenant chaque valeur, et le retourner, ce qui peut conduire
|
|
à des tableaux de taille importante : par exemple, l'appel du code
|
|
<command>range(0, 1000000)</command> peut consommer nettement plus de
|
|
100 Mo de mémoire.
|
|
</para>
|
|
|
|
<para>
|
|
Comme alternative, nous pouvons implémenter un générateur
|
|
<literal>xrange()</literal>, qui n'aura en besoin mémoire que la seule
|
|
création d'un objet <classname>Iterator</classname>, et devra garder trace
|
|
en interne du statut courant du générateur, ce qui revient à une
|
|
consommation mémoire inférieure à 1 Ko.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Implémentation de la fonction <function>range</function> sous la forme d'un générateur</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function xrange($start, $limit, $step = 1) {
|
|
if ($start <= $limit) {
|
|
if ($step <= 0) {
|
|
throw new LogicException('Step must be positive');
|
|
}
|
|
|
|
for ($i = $start; $i <= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
} else {
|
|
if ($step >= 0) {
|
|
throw new LogicException('Step must be negative');
|
|
}
|
|
|
|
for ($i = $start; $i >= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Il est à noter que les fonctions range() et xrange() produisent le
|
|
* même affichage, ci-dessous.
|
|
*/
|
|
|
|
echo 'Nombres impairs à un seul chiffre depuis range() : ';
|
|
foreach (range(1, 9, 2) as $number) {
|
|
echo "$number ";
|
|
}
|
|
echo "\n";
|
|
|
|
echo 'Nombres impairs à un seul chiffre depuis xrange() : ';
|
|
foreach (xrange(1, 9, 2) as $number) {
|
|
echo "$number ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
Nombres impairs à un seul chiffre depuis range() : 1 3 5 7 9
|
|
Nombres impairs à un seul chiffre depuis xrange() : 1 3 5 7 9
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<sect2 xml:id="language.generators.object">
|
|
<title>Les objets <classname>Generator</classname></title>
|
|
<para>
|
|
Lorsqu'une fonction générateur est appelée,
|
|
un objet de la classe interne <classname>Generator</classname> est
|
|
retournée. Cet objet implémente l'interface <classname>Iterator</classname>
|
|
de la même façon qu'un objet itérateur, qui avance uniquement, le ferait,
|
|
et fournit les méthodes qui peuvent être appelées pour manipuler le statut
|
|
du générateur, y compris l'envoi des valeurs et leurs retours.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.generators.syntax">
|
|
<title>Syntaxe d'un Générateur</title>
|
|
|
|
<para>
|
|
Une fonction générateur ressemble à une fonction normale, sauf qu'au lieu de
|
|
retourner une valeur, un générateur &yield; retourne autant de valeurs que nécessaire.
|
|
Toutes fonctions contenant &yield; est une fonction générateur.
|
|
</para>
|
|
|
|
<para>
|
|
Lorsqu'une fonction générateur est appelée, elle retourne un objet
|
|
que l'on peut parcourir. Lorsque vous parcourez cet objet (par exemple, via une
|
|
boucle &foreach;), PHP appellera les méthodes d'itération de l'objet chaque
|
|
fois qu'il a besoin d'une valeur, puis sauvegarde le statut du générateur
|
|
lorsqu'il génère une valeur, pour qu'il puisse être repris lorsque
|
|
la prochaine valeur sera requise.
|
|
</para>
|
|
|
|
<para>
|
|
Lorsqu'il n'y a plus de valeur à fournir, la fonction générateur peut simplement
|
|
retourner, et le code appelant continuera comme si un tableau n'avait plus de valeur.
|
|
</para>
|
|
|
|
<note>
|
|
<para>
|
|
Un générateur peut retourner des valeurs,
|
|
qui peuvent être récupérées en utilisant <methodname>Generator::getReturn</methodname>.
|
|
</para>
|
|
</note>
|
|
|
|
<sect2 xml:id="control-structures.yield">
|
|
<title>Le mot-clé <command>yield</command></title>
|
|
|
|
<para>
|
|
Le mot clé <command>yield</command> est le cœur d'une fonction générateur.
|
|
Dans sa forme la plus simple, une instruction yield ressemble à une instruction
|
|
return, excepté qu'au lieu de stopper l'exécution de la fonction
|
|
et de retourner, yield fournit une valeur au code parcourant le générateur,
|
|
et met en pause l'exécution de la fonction générateur.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Un exemple simple de production de valeurs</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function gen_one_to_three() {
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
// Notez que $i est préservé entre chaque production de valeur.
|
|
yield $i;
|
|
}
|
|
}
|
|
|
|
$generator = gen_one_to_three();
|
|
foreach ($generator as $value) {
|
|
echo "$value\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1
|
|
2
|
|
3
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<note>
|
|
<para>
|
|
En interne, des clés séquentielles entières seront associées
|
|
avec les valeurs produites, de la même manière que pour un
|
|
tableau non-associatif.
|
|
</para>
|
|
</note>
|
|
|
|
<sect3 xml:id="control-structures.yield.associative">
|
|
<title>Fourniture de valeurs avec des clés</title>
|
|
|
|
<para>
|
|
PHP supporte également les tableaux associatifs, et les générateurs
|
|
ne sont pas différents. En plus de fournir des valeurs simples, comme nous
|
|
l'avons vu plus haut, vous pouvez aussi, en même temps, fournir une clé.
|
|
</para>
|
|
|
|
<para>
|
|
La syntaxe permettant de produire une paire clé/valeur est similaire à celle utilisée
|
|
pour définir un tableau associatif ; comme ceci :
|
|
</para>
|
|
|
|
<example>
|
|
<title>Production d'une paire clé/valeur</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/*
|
|
* L'entrée est constituée de champs séparés par un point-virgule,
|
|
* et le premier champ est un ID à utiliser comme clé.
|
|
*/
|
|
|
|
$input = <<<'EOF'
|
|
1;PHP;Likes dollar signs
|
|
2;Python;Likes whitespace
|
|
3;Ruby;Likes blocks
|
|
EOF;
|
|
|
|
function input_parser($input) {
|
|
foreach (explode("\n", $input) as $line) {
|
|
$fields = explode(';', $line);
|
|
$id = array_shift($fields);
|
|
|
|
yield $id => $fields;
|
|
}
|
|
}
|
|
|
|
foreach (input_parser($input) as $id => $fields) {
|
|
echo "$id:\n";
|
|
echo " $fields[0]\n";
|
|
echo " $fields[1]\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1:
|
|
PHP
|
|
Likes dollar signs
|
|
2:
|
|
Python
|
|
Likes whitespace
|
|
3:
|
|
Ruby
|
|
Likes blocks
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.null">
|
|
<title>Production de valeurs nulles</title>
|
|
|
|
<para>
|
|
Yield peut être appelé sans argument pour fournir une valeur nulle
|
|
avec une clé automatique.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Production de valeurs nulles</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function gen_three_nulls() {
|
|
foreach (range(1, 3) as $i) {
|
|
yield;
|
|
}
|
|
}
|
|
|
|
var_dump(iterator_to_array(gen_three_nulls()));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
array(3) {
|
|
[0]=>
|
|
NULL
|
|
[1]=>
|
|
NULL
|
|
[2]=>
|
|
NULL
|
|
}
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.references">
|
|
<title>Production de valeurs par référence</title>
|
|
|
|
<para>
|
|
Les fonctions générateur peuvent produire des valeurs par référence.
|
|
Ceci se fait de la même façon que le
|
|
<link linkend="functions.returning-values">retour par référence
|
|
depuis des fonctions</link> : en ajoutant un ET commercial (&) au nom
|
|
de la fonction.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Production de valeurs par référence</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function &gen_reference() {
|
|
$value = 3;
|
|
|
|
while ($value > 0) {
|
|
yield $value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Notez qu'il est possible de changer $number dans la boucle,
|
|
* et, du fait que le générateur fournit des références, $value
|
|
* dans gen_reference() change aussi.
|
|
*/
|
|
foreach (gen_reference() as &$number) {
|
|
echo (--$number).'... ';
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
2... 1... 0...
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.from">
|
|
<title>Délégation du générateur via <command>yield from</command></title>
|
|
|
|
<para>
|
|
La délégation du générateur vous permet d'obtenir les valeurs d'un
|
|
autre générateur, d'un objet <classname>Traversable</classname>, ou
|
|
d'un <type>array</type> en utilisant le mot clé <command>yield from</command>.
|
|
Le générateur externe va ainsi obtenir toutes les valeurs du générateur
|
|
interne, de l'objet, ou du tableau tant qu'il n'est pas invalide, après
|
|
quoi, l'exécution va continuer dans le générateur externe.
|
|
</para>
|
|
|
|
<para>
|
|
Si un générateur est utilisé avec l'expression <command>yield from</command>,
|
|
l'expression <command>yield from</command> va aussi retourner toute valeur
|
|
retournée par le générateur interne.
|
|
</para>
|
|
|
|
<caution>
|
|
<title>Stocker dans un tableau (e.g. avec <function>iterator_to_array</function>)</title>
|
|
|
|
<para>
|
|
<command>yield from</command> ne réinitialise pas les
|
|
clés. Il préserve les clés retournées par l'objet
|
|
<classname>Traversable</classname>, ou <type>array</type>.
|
|
Donc, certaines valeurs peuvent partager une clé commune avec d'autres <command>yield</command> ou
|
|
<command>yield from</command>, qui, lors de l'insertion
|
|
dans un tableau, écrasera les anciennes valeurs avec cette clé.
|
|
</para>
|
|
|
|
<para>
|
|
Un cas fréquent où cela est important est <function>iterator_to_array</function>
|
|
renvoyant un tableau avec clé par défaut, conduisant à
|
|
des résultats éventuellement inattendus.
|
|
<function>iterator_to_array</function> a un second paramètre
|
|
<parameter>preserve_keys</parameter> qui peut être défini sur &false;
|
|
pour collecter toutes les valeurs en ignorant les clés
|
|
renvoyées par le <classname>Generator</classname>.
|
|
</para>
|
|
|
|
<example>
|
|
<title><command>yield from</command> avec <function>iterator_to_array</function></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function inner() {
|
|
yield 1; // clé 0
|
|
yield 2; // clé 1
|
|
yield 3; // clé 2
|
|
}
|
|
function gen() {
|
|
yield 0; // clé 0
|
|
yield from inner(); // clés 0-2
|
|
yield 4; // clé 1
|
|
}
|
|
// met à false le second paramètre pour avoir un tableau [0, 1, 2, 3, 4]
|
|
var_dump(iterator_to_array(gen()));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
array(3) {
|
|
[0]=>
|
|
int(1)
|
|
[1]=>
|
|
int(4)
|
|
[2]=>
|
|
int(3)
|
|
}
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</caution>
|
|
|
|
<example>
|
|
<title>Utilisation basique de <command>yield from</command></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function count_to_ten() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from seven_eight();
|
|
yield 9;
|
|
yield 10;
|
|
}
|
|
|
|
function seven_eight() {
|
|
yield 7;
|
|
yield from eight();
|
|
}
|
|
|
|
function eight() {
|
|
yield 8;
|
|
}
|
|
|
|
foreach (count_to_ten() as $num) {
|
|
echo "$num ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1 2 3 4 5 6 7 8 9 10
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<example>
|
|
<title><command>yield from</command> et les valeurs retournées</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function count_to_ten() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from seven_eight();
|
|
return yield from nine_ten();
|
|
}
|
|
|
|
function seven_eight() {
|
|
yield 7;
|
|
yield from eight();
|
|
}
|
|
|
|
function eight() {
|
|
yield 8;
|
|
}
|
|
|
|
function nine_ten() {
|
|
yield 9;
|
|
return 10;
|
|
}
|
|
|
|
$gen = count_to_ten();
|
|
foreach ($gen as $num) {
|
|
echo "$num ";
|
|
}
|
|
echo $gen->getReturn();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1 2 3 4 5 6 7 8 9 10
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.generators.comparison">
|
|
<title>Comparaison des générateurs avec les objets <classname>Iterator</classname></title>
|
|
|
|
<para>
|
|
Le principal avantage des générateurs est leur simplicité. Moins de code
|
|
doit être écrit que lorsqu'il s'agit d'implémenter une classe
|
|
<classname>Iterator</classname>, et il est généralement plus lisible.
|
|
Par exemple, la fonction et la classe suivante sont équivalentes :
|
|
</para>
|
|
|
|
<informalexample>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function getLinesFromFile($fileName) {
|
|
if (!$fileHandle = fopen($fileName, 'r')) {
|
|
return;
|
|
}
|
|
|
|
while (false !== $line = fgets($fileHandle)) {
|
|
yield $line;
|
|
}
|
|
|
|
fclose($fileHandle);
|
|
}
|
|
|
|
// versus...
|
|
|
|
class LineIterator implements Iterator {
|
|
protected $fileHandle;
|
|
|
|
protected $line;
|
|
protected $i;
|
|
|
|
public function __construct($fileName) {
|
|
if (!$this->fileHandle = fopen($fileName, 'r')) {
|
|
throw new RuntimeException('Impossible d\'ouvrir le fichier : "' . $fileName . '"');
|
|
}
|
|
}
|
|
|
|
public function rewind() {
|
|
fseek($this->fileHandle, 0);
|
|
$this->line = fgets($this->fileHandle);
|
|
$this->i = 0;
|
|
}
|
|
|
|
public function valid() {
|
|
return false !== $this->line;
|
|
}
|
|
|
|
public function current() {
|
|
return $this->line;
|
|
}
|
|
|
|
public function key() {
|
|
return $this->i;
|
|
}
|
|
|
|
public function next() {
|
|
if (false !== $this->line) {
|
|
$this->line = fgets($this->fileHandle);
|
|
$this->i++;
|
|
}
|
|
}
|
|
|
|
public function __destruct() {
|
|
fclose($this->fileHandle);
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>
|
|
Cependant, cette flexibilité a un coût : les générateurs sont des itérateurs
|
|
n'allant que vers l'avant, et ils ne peuvent pas être ré-initialisés une fois
|
|
leur parcours commencé. Cela signifie également que le même générateur ne peut
|
|
pas être utilisé à plusieurs reprises : le générateur devra être reconstruit
|
|
en appelant une nouvelle fois la fonction générateur.
|
|
</para>
|
|
|
|
<simplesect role="seealso">
|
|
&reftitle.seealso;
|
|
<para>
|
|
<simplelist>
|
|
<member><link linkend="language.oop5.iterations">Itération d'Objet</link></member>
|
|
</simplelist>
|
|
</para>
|
|
</simplesect>
|
|
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|