mirror of
https://github.com/php/doc-de.git
synced 2026-03-23 23:02:13 +01:00
621 lines
16 KiB
XML
621 lines
16 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: 08e58ace7e5b538c8ed75d784a54885d5f785d30 Maintainer: cmb Status: ready -->
|
|
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
|
|
<title>Generatoren</title>
|
|
|
|
<sect1 xml:id="language.generators.overview">
|
|
<title>Generatoren-Übersicht</title>
|
|
<?phpdoc print-version-for="generators"?>
|
|
|
|
<para>
|
|
Generatoren bieten eine einfache Möglichkeit, um einfache
|
|
<link linkend="language.oop5.iterations">Iteratoren</link> zu erstellen,
|
|
ohne den Overhead oder die Komplexität der Erstellung einer Klasse zu
|
|
haben, die das <classname>Iterator</classname>-Interface implementiert.
|
|
</para>
|
|
|
|
<para>
|
|
Mit einem Generator können auf bequeme Weise Daten für &foreach; -Schleifen
|
|
bereitgestellt werden, ohne ein Array im Speicher zu erzeugen, was dazu
|
|
führen kann, dass das Programm ein Speicherlimit überschreitet oder
|
|
beträchtliche Prozessorzeit benötigt. Alternativ kann eine Generatorfunktion
|
|
verwendet werden, die einer normalen
|
|
<link linkend="functions.user-defined">Funktion</link> entspricht, bei der
|
|
aber keine einmalige
|
|
<link linkend="functions.returning-values">Rückgabe</link> erfolgt, sondern
|
|
der Generator so oft wie nötig einen Wert abgibt (Stichwort: &yield;), um
|
|
die Werte zu liefern, über die iteriert werden soll.
|
|
Wie bei Iteratoren ist ein wahlfreier Datenzugriff nicht möglich.
|
|
</para>
|
|
|
|
<para>
|
|
Ein einfaches Beispiel dazu ist, die <function>range</function>-Funktion
|
|
durch einen Generator neu zu implementieren. Die
|
|
Standard-<function>range</function>-Funktion generiert und liefert Arrays,
|
|
welche jeden Wert enthalten, was große Arrays zur Folge haben kann: zum
|
|
Beispiel hat der Aufruf <command>range(0, 1000000)</command> zur Folge,
|
|
dass weit über 100 MB an Speicher benötigt werden.
|
|
</para>
|
|
|
|
<para>
|
|
Als Alternative können wir einen <literal>xrange()</literal>-Generator
|
|
implementieren, welcher immer nur genug Speicher benötigt, um ein
|
|
<classname>Iterator</classname>-Objekt zu erzeugen und intern den aktuellen
|
|
Zustand des Generators zu verfolgen, was sich als weniger als 1 Kilobyte
|
|
herausstellt.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Implementierung von <function>range</function> als Generator</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function xrange($start, $limit, $step = 1) {
|
|
if ($start <= $limit) {
|
|
if ($step <= 0) {
|
|
throw new LogicException('Schrittweite muss positiv sein');
|
|
}
|
|
|
|
for ($i = $start; $i <= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
} else {
|
|
if ($step >= 0) {
|
|
throw new LogicException('Schrittweite muss negativ sein');
|
|
}
|
|
|
|
for ($i = $start; $i >= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hinweis: sowohl range() als auch xrange()
|
|
* erzeugen die gleiche Ausgabe.
|
|
*/
|
|
|
|
echo 'Einstellige ungerade Zahlen von range(): ';
|
|
foreach (range(1, 9, 2) as $zahl) {
|
|
echo "$zahl ";
|
|
}
|
|
echo "\n";
|
|
|
|
echo 'Einstellige ungerade Zahlen von xrange(): ';
|
|
foreach (xrange(1, 9, 2) as $zahl) {
|
|
echo "$zahl ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
Einstellige ungerade Zahlen von range(): 1 3 5 7 9
|
|
Einstellige ungerade Zahlen von xrange(): 1 3 5 7 9
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<sect2 xml:id="language.generators.object">
|
|
<title><classname>Generator</classname>-Objekte</title>
|
|
<para>
|
|
Beim Aufruf einer Generatorfunktion wird ein neues Objekt der internen
|
|
<classname>Generator</classname>-Klasse zurückgegeben. Dieses Objekt
|
|
implementiert das <classname>Iterator</classname>-Interface in gleicher
|
|
Weise wie es ein forward-only Iterator-Objekt machen würde und stellt
|
|
Methoden zur Verfügung, die aufgerufen werden können, um den Zustand des
|
|
Generators zu manipulieren, einschließlich des Sendens von Werten an,
|
|
und der Rückgabe von Werten von ihm.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.generators.syntax">
|
|
<title>Generator-Syntax</title>
|
|
|
|
<para>
|
|
Eine Generatorfunktion sieht genau so aus wie eine normale Funktion mit
|
|
der Ausnahme, dass ein Generator statt eines Wertes so viele Werte wie
|
|
nötig zurückgibt (Stichwort: &yield;).
|
|
Jede Funktion, die &yield; enthält, ist eine Generatorfunktion.
|
|
</para>
|
|
|
|
<para>
|
|
Wenn eine Generatorfunktion aufgerufen wird, wird ein Objekt zurückgegeben,
|
|
über das iteriert werden kann. Wenn Sie über dieses Objekt iterieren (zum
|
|
Beispiel, per &foreach;-Schleife), wird PHP die Iteratorfunktionen des
|
|
Objekts jedes Mal aufrufen, wenn ein Wert benötigt wird. Dann wird der
|
|
Status des Generators gesichert, so dass fortgefahren werden kann, wenn der
|
|
nächste Wert benötigt wird.
|
|
</para>
|
|
|
|
<para>
|
|
Sobald keine weiteren Werte zurückgegeben werden können, kann der
|
|
Generator einfach zurückgeben, und der rufende Code wird
|
|
fortgesetzt, als gäbe es keine weiteren Werte in einem Array.
|
|
</para>
|
|
|
|
<note>
|
|
<para>
|
|
Ein Generator kann Werte zurückgeben, die unter Verwendung von
|
|
<methodname>Generator::getReturn</methodname> ermittelt werden können.
|
|
</para>
|
|
</note>
|
|
|
|
<sect2 xml:id="control-structures.yield">
|
|
<title><command>yield</command>-Schlüsselwort</title>
|
|
|
|
<para>
|
|
Das Herz einer Generatorfunktion ist das
|
|
<command>yield</command>-Schlüsselwort. In seiner einfachsten Form sieht
|
|
das yield-Schlüsselwort wie eine return-Anweisung aus, ausser dass die
|
|
Ausführung mit der Rückgabe nicht beendet wird, sondern yield stattdessen
|
|
bei der Schleife über den Generator einen Wert für den Code bereitstellt
|
|
und die Ausführung der Generatorfunktion anhält.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Ein einfaches Beispiel zum liefern (yielding) von Werten</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function generiere_eins_bis_drei() {
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
// Hinweis: $i bleibt zwischen den yields erhalten.
|
|
yield $i;
|
|
}
|
|
}
|
|
|
|
$generator = generiere_eins_bis_drei();
|
|
foreach ($generator as $wert) {
|
|
echo "$wert\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1
|
|
2
|
|
3
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<note>
|
|
<para>
|
|
Intern werden sequentielle Integer-Schlüssel mit den abgelieferten Werten
|
|
verknüpft, so wie mit einem nicht-assoziativen Array.
|
|
</para>
|
|
</note>
|
|
|
|
<sect3 xml:id="control-structures.yield.associative">
|
|
<title>Produzieren von Werten mit Schlüsseln</title>
|
|
|
|
<para>
|
|
PHP unterstützt ebenfalls assoziative Arrays, und Generatoren
|
|
unterscheiden sich nicht davon. Als Ergänzung zum Produzieren einfacher
|
|
Werte, wie oben gezeigt, können Sie zur gleichen Zeit auch einen
|
|
Schlüssel liefern.
|
|
</para>
|
|
|
|
<para>
|
|
Die Syntax für das Produzieren eines Schlüssel/Wert-Paares ist sehr
|
|
ähnlich wie die Definition von assoziativen Arrays, wie unten gezeigt.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzieren eines Schlüssel/Wert-Paares</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/*
|
|
* Die Eingabe sind Semikolon getrennte Felder, wobei das erste Feld
|
|
* eine ID ist, die als Schlüssel genutzt wird.
|
|
*/
|
|
|
|
$eingabe = <<<'EOF'
|
|
1;PHP;mag Dollarzeichen
|
|
2;Python;mag Leerzeichen
|
|
3;Ruby;mag Blöcke
|
|
EOF;
|
|
|
|
function eingabe_parser($eingabe) {
|
|
foreach (explode("\n", $eingabe) as $zeile) {
|
|
$felder = explode(';', $zeile);
|
|
$id = array_shift($felder);
|
|
|
|
yield $id => $felder;
|
|
}
|
|
}
|
|
|
|
foreach (eingabe_parser($eingabe) as $id => $felder) {
|
|
echo "$id:\n";
|
|
echo " $felder[0]\n";
|
|
echo " $felder[1]\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1:
|
|
PHP
|
|
mag Dollarzeichen
|
|
2:
|
|
Python
|
|
mag Leerzeichen
|
|
3:
|
|
Ruby
|
|
mag Blöcke
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.null">
|
|
<title>Produzieren von null-Werten</title>
|
|
|
|
<para>
|
|
Yield kann ohne Argument aufgerufen werden, um einen &null;-Wert mit
|
|
einem automatischen Schlüssel zurückzugeben.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzieren von &null;s</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function generiere_drei_nulls() {
|
|
foreach (range(1, 3) as $i) {
|
|
yield;
|
|
}
|
|
}
|
|
|
|
var_dump(iterator_to_array(generiere_drei_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>Produzieren als Referenz</title>
|
|
|
|
<para>
|
|
Generatorfunktionen sind genauso in der Lage Werte als Referenz
|
|
zurückzugeben, wie als Wert. Dies kann in gleicher Weise erfolgen, wie
|
|
beim
|
|
<link linkend="functions.returning-values">Zurückgeben von Referenzen aus Funktionen</link>:
|
|
dies geschieht, indem dem Funktionsnamen ein Kaufmanns-Und vorangestellt
|
|
wird.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzieren von Werten als Referenz</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function &generiere_referenz() {
|
|
$wert = 3;
|
|
|
|
while ($wert > 0) {
|
|
yield $wert;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hinweis: wir können $nummer innerhalb der Schleife ändern,
|
|
* und weil der Generator Referenzen zurückgibt, wird $wert
|
|
* innerhalb von generiere_referenz() verändert.
|
|
*/
|
|
foreach (generiere_referenz() as &$nummer) {
|
|
echo (--$nummer).'... ';
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
2... 1... 0...
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.from">
|
|
<title>Generatordelegation per <command>yield from</command></title>
|
|
|
|
<para>
|
|
Die Generatordelegation ermöglicht, mittels
|
|
<command>yield from</command>-Ausdruck Werte von einem anderen Generator,
|
|
<classname>Traversable</classname>-Objekt oder <type>Array</type>
|
|
liefern zu lassen. Der äußere Generator liefert dann alle Werte vom
|
|
inneren Generator, Objekt oder Array, bis dies nicht mehr gültig
|
|
ist und die Ausführung im äußeren Generator fortfährt.
|
|
</para>
|
|
|
|
<para>
|
|
Falls ein Generator mit <command>yield from</command> verwendet wird,
|
|
gibt der <command>yield from</command>-Ausdruck auch alle Werte zurück,
|
|
die vom inneren Generator zurückgegeben werden.
|
|
</para>
|
|
|
|
<caution>
|
|
<title>Speichern in ein Array (&zb; mit <function>iterator_to_array</function>)</title>
|
|
|
|
<para>
|
|
<command>yield from</command> setzt nicht die Schlüssel zurück. Es erhält
|
|
die Schlüssel, die vom <classname>Traversable</classname>-Objekt oder
|
|
<type>Array</type> zurückgegeben wurden. Daher können einige Werte den
|
|
selben Schlüssel mit einem anderen <command>yield</command> oder
|
|
<command>yield from</command> gemein haben, der, bei der Einfügung in ein
|
|
Array, vorherige Werte mit diesem Schlüssel überschreibt.
|
|
</para>
|
|
|
|
<para>
|
|
Ein üblicher Fall, für den dies relevant ist, ist
|
|
<function>iterator_to_array</function>, das standardmäßig ein indexiertes
|
|
Array zurück gibt, was zu möglicherweise unerwarteten Ergebnissen führen
|
|
kann. <function>iterator_to_array</function> hat einen zweiten Parameter
|
|
<parameter>preserve_keys</parameter>, der auf &false; gesetzt werden
|
|
kann, um alle Werte zu sammeln, während die Schlüssel, die vom
|
|
<classname>Generator</classname> geliefert werden, ignoriert werden.
|
|
</para>
|
|
|
|
<example>
|
|
<title><command>yield from</command> mit <function>iterator_to_array</function></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function inner() {
|
|
yield 1; // Schlüssel 0
|
|
yield 2; // Schlüssel 1
|
|
yield 3; // Schlüssel 2
|
|
}
|
|
function gen() {
|
|
yield 0; // Schlüssel 0
|
|
yield from inner(); // Schlüssel 0-2
|
|
yield 4; // Schlüssel 1
|
|
}
|
|
// Übergib false als zweiten Parameter, um das Array [0, 1, 2, 3, 4] zu erhalten
|
|
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>Grundlegende Verwendung von <command>yield from</command></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function zaehle_bis_zehn() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from sieben_acht();
|
|
yield 9;
|
|
yield 10;
|
|
}
|
|
|
|
function sieben_acht() {
|
|
yield 7;
|
|
yield from acht();
|
|
}
|
|
|
|
function acht() {
|
|
yield 8;
|
|
}
|
|
|
|
foreach (zaehle_bis_zehn() as $zahl) {
|
|
echo "$zahl ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1 2 3 4 5 6 7 8 9 10
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<example>
|
|
<title><command>yield from</command> und Rückgabewerte</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function zaehle_bis_zehn() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from sieben_acht();
|
|
return yield from neun_zehn();
|
|
}
|
|
|
|
function sieben_acht() {
|
|
yield 7;
|
|
yield from acht();
|
|
}
|
|
|
|
function acht() {
|
|
yield 8;
|
|
}
|
|
|
|
function neun_zehn() {
|
|
yield 9;
|
|
return 10;
|
|
}
|
|
|
|
$gen = zaehle_bis_zehn();
|
|
foreach ($gen as $zahl) {
|
|
echo "$zahl ";
|
|
}
|
|
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>Vergleich von Generatoren mit <classname>Iterator</classname>-Objekten</title>
|
|
|
|
<para>
|
|
Der Hauptvorteil von Generatoren ist deren Einfachheit. Viel weniger
|
|
Boilerplate-Code muss geschrieben werden als im Vergleich zur
|
|
Implementierung einer <classname>Iterator</classname>-Klasse und der Code
|
|
ist generell einfacher zu lesen. Zum Beispiel sind die folgende Funktion und
|
|
Klasse äquivalent:
|
|
</para>
|
|
|
|
<informalexample>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function leseZeilenVonDatei($dateiName) {
|
|
if (!$dateiHandle = fopen($dateiName, 'r')) {
|
|
return;
|
|
}
|
|
|
|
while (false !== $zeile = fgets($dateiHandle)) {
|
|
yield $zeile;
|
|
}
|
|
|
|
fclose($dateiHandle);
|
|
}
|
|
|
|
// im Gegensatz zu...
|
|
|
|
class LineIterator implements Iterator {
|
|
class LineIterator implements Iterator {
|
|
protected $fileHandle;
|
|
|
|
protected $line;
|
|
protected $i;
|
|
|
|
public function __construct($fileName) {
|
|
if (!$this->fileHandle = fopen($fileName, 'r')) {
|
|
throw new RuntimeException('Couldn\'t open file "' . $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>
|
|
Diese Flexibilität kommt allerdings nicht ohne Preis: Generatoren sind
|
|
forward-only-Iteratoren, und können nicht zurückgesetzt werden, wenn sie
|
|
einmal gestartet wurden. Das heißt außerdem, dass der selbe Generator nicht
|
|
mehrfach iteriert werden kann: der Generator muss durch einen
|
|
erneuten Aufruf der Generator-Funktion neu erstellt werden.
|
|
</para>
|
|
|
|
<simplesect role="seealso">
|
|
&reftitle.seealso;
|
|
<para>
|
|
<simplelist>
|
|
<member><link linkend="language.oop5.iterations">Objektiteration</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
|
|
-->
|