1
0
mirror of https://github.com/php/doc-de.git synced 2026-03-23 23:02:13 +01:00
Files
archived-doc-de/language/generators.xml
Martin Samesch 1a7bccfdc4 Sync with EN
2024-10-18 22:31:08 +02:00

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