1
0
mirror of https://github.com/php/doc-ru.git synced 2026-03-23 23:32:16 +01:00
Files
archived-doc-ru/language/generators.xml

643 lines
20 KiB
XML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 08e58ace7e5b538c8ed75d784a54885d5f785d30 Maintainer: rjhdby Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
<title>Генераторы</title>
<sect1 xml:id="language.generators.overview">
<title>Знакомство с генераторами</title>
<?phpdoc print-version-for="generators"?>
<para>
Генераторы — легкий способ реализации простых
<link linkend="language.oop5.iterations">итераторов</link>
без дополнительных ресурсов или сложностей, которые связаны с написанием класса,
который реализует интерфейс <classname>Iterator</classname>.
</para>
<para>
Генератор поддерживает удобную передачу данных в циклы &foreach;
без предварительной загрузки массива в память, что иногда
вызывает превышение программой предела памяти или значительно увеличивает время обработки,
которое уходит на генерацию результата. Вместо этого пишут функцию-генератор,
которая аналогична стандартной <link linkend="functions.user-defined">функции</link>,
за исключением того, что вместо <link linkend="functions.returning-values">возврата</link>
единого значения с результатом работы цикла, генератор через ключевое слово &yield; умеет
выдавать результат столько раз, сколько значений требуется перебрать.
Как и итераторы, генераторы не поддерживают произвольный доступ к элементам массива.
</para>
<para>
Простой пример этого — переопределение функции
<function>range</function> как генератора. Стандартная функция
<function>range</function> генерирует массив, который состоит из значений, и
возвращает его, что приводит к генерации больших массивов: например,
вызов <command>range(0, 1000000)</command> займёт более 100 МБ
оперативной памяти.
</para>
<para>
В качестве альтернативы можно создать генератор <literal>xrange()</literal>,
который использует память только чтобы создать объект
<classname>Iterator</classname> и сохранить текущее состояние, что потребует
не больше 1 килобайта памяти.
</para>
<example>
<title>Реализация функции <function>range</function> как генератора</title>
<programlisting role="php">
<![CDATA[
<?php
function xrange($start, $limit, $step = 1)
{
if ($start <= $limit) {
if ($step <= 0) {
throw new LogicException('Шаг должен быть положительным');
}
for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if ($step >= 0) {
throw new LogicException('Шаг должен быть отрицательным');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
/* Обратите внимание, что и range() и xrange() дадут один и тот же вывод */
echo 'Нечётные однозначные числа с помощью range(): ';
foreach (range(1, 9, 2) as $number) {
echo "$number ";
}
echo "\n";
echo 'Нечётные однозначные числа через функцию xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
echo "$number ";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Нечётные однозначные числа через функцию range(): 1 3 5 7 9
Нечётные однозначные числа через функцию xrange(): 1 3 5 7 9
]]>
</screen>
</example>
<sect2 xml:id="language.generators.object">
<title>Объект <classname>Generator</classname></title>
<para>
Когда функция-генератор вызывается, она возвращает объект встроенного
класса <classname>Generator</classname>. Этот объект реализует интерфейс
<classname>Iterator</classname>, станет однонаправленным объектом итератора
и предоставит методы, с помощью которых можно управлять его состоянием, включая
передачу в него и возвращения из него значений.
</para>
</sect2>
</sect1>
<sect1 xml:id="language.generators.syntax">
<title>Синтаксис генераторов</title>
<para>
Функция-генератор выглядит как обычная функция, за исключением того, что
вместо возврата значения генератор выдаёт столько значений, столько
ему необходимо.
Каждая функция с оператором &yield; — функция-генератор.
</para>
<para>
Когда вызывается генератор, он возвращает объект, который можно итерировать.
При итерации по этому объекту (например, в цикле &foreach;), PHP вызывает
методы итерации объекта каждый раз, когда ему требуется значение, а затем
сохраняет состояние генератора и при следующем вызове возвращает следующее
значение.
</para>
<para>
Когда значения в генераторе закончатся, генератор может просто выполнить возврат,
и вызывающий код продолжится так же, как если бы в массиве закончились
значения.
</para>
<note>
<para>
Генераторы умеют возвращать значения, которые можно получить
методом <methodname>Generator::getReturn</methodname>.
</para>
</note>
<sect2 xml:id="control-structures.yield">
<title>Ключевое слово <command>yield</command></title>
<para>
Сердце функции-генератора — ключевое слово <command>yield</command>.
В простейшей форме инструкция yield похожа на инструкцию
return, за исключением того, что вместо остановки выполнения функции и возврата,
yield отдаёт значение коду, который выполняет цикл над генератором,
и приостанавливает выполнение функции генератора.
</para>
<example>
<title>Простой пример выдачи значений</title>
<programlisting role="php">
<![CDATA[
<?php
function gen_one_to_three()
{
for ($i = 1; $i <= 3; $i++) {
// Обратите внимание, что переменная $i сохраняет значение между вызовами
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>
Внутренне последовательные целочисленные ключи свяжутся с полученными значениями,
как и в случае с неассоциативным массивом.
</para>
</note>
<sect3 xml:id="control-structures.yield.associative">
<title>Получение значений с ключами</title>
<para>
PHP также поддерживает ассоциативные массивы, и генераторы — не исключение.
Помимо получения простых значений, как показывает пример, разрешается также
одновременно получить ключ.
</para>
<para>
Синтаксис получения пары ключ и значение очень похож на синтаксис определения
ассоциативных массивов, как показывает следующий пример.
</para>
<example>
<title>Получение пар ключ/значение</title>
<programlisting role="php">
<![CDATA[
<?php
/* Переменная $input содержит пары ключ и значение, которые разделили точкой с запятой */
$input = <<<'EOF'
1;PHP;Любит знаки доллара
2;Python;Любит пробелы
3;Ruby;Любит блоки
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
Любит знаки доллара
2:
Python
Любит пробелы
3:
Ruby
Любит блоки
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.null">
<title>Получение значений null</title>
<para>
Чтобы получить значение &null;, нужно вызвать yield без аргументов. Ключ сгенерируется
автоматически.
</para>
<example>
<title>Получение &null;</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>Получение значения по ссылке</title>
<para>
Функции-генераторы умеют возвращать значения как по ссылке, так и по значению. Это делается так же,
как и <link linkend="functions.returning-values">возврат ссылок из функций</link>:
добавлением амперсанда (&amp;) к имени функции.
</para>
<example>
<title>Получение значений по ссылке</title>
<programlisting role="php">
<![CDATA[
<?php
function &gen_reference()
{
$value = 3;
while ($value > 0) {
yield $value;
}
}
/* Обратите внимание, что можно изменять значение переменной $number в цикле,
* и поскольку генератор возвращает ссылку, переменная $value
* в функции gen_reference() также изменится. */
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>Делегирование генератора через <command>yield from</command></title>
<para>
Делегирование генератора позволяет получать значения
из другого генератора, объекта <classname>Traversable</classname> или массива
через ключевые слова <command>yield from</command>.
Внешний генератор будет возвращать значения из внутреннего генератора,
объекта или массива до тех пор, пока они не перестанут действовать, после чего
выполнение продолжится во внешнем генераторе.
</para>
<para>
Если генератор используется с ключевыми словами <command>yield from</command>, выражение
<command>yield from</command> также будет возвращать значения из
внутреннего генератора.
</para>
<caution>
<title>Сохранение в массив (например, через функцию <function>iterator_to_array</function>)</title>
<para>
Ключевые слова <command>yield from</command> не сбрасывают ключи. Ключи, которые вернул
объект <classname>Traversable</classname> или массив, сохранятся.
Поэтому некоторые значения могут пересекаться по ключам с другими выражениями
<command>yield</command> или <command>yield from</command>, что при записи
в массив перезапишет прежние значения этим ключом.
</para>
<para>
Распространенный случай, когда это имеет значение, — функция <function>iterator_to_array</function>,
которая возвращает массив с ключом по умолчанию, что иногда приводит
к неожиданным результатам. У функции <function>iterator_to_array</function> есть второй параметр
<parameter>preserve_keys</parameter>, которому можно присвоить значение &false;
для генерации собственных ключей и игнорирования ключей,
которые передаются из объекта <classname>Generator</classname>.
</para>
<example>
<title>Выражение <command>yield from</command> с функцией <function>iterator_to_array</function></title>
<programlisting role="php">
<![CDATA[
<?php
function inner()
{
yield 1; // Ключ 0
yield 2; // Ключ 1
yield 3; // Ключ 2
}
function gen()
{
yield 0; // Ключ 0
yield from inner(); // Ключи 0-2
yield 4; // Ключ 1
}
// Задайте false вторым параметром для получения массива [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>Основы работы с выражением <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> и возвращаемые значения</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>Сравнение генераторов с объектами класса <classname>Iterator</classname></title>
<para>
Главное преимущество генераторов — простота. Требуется написать гораздо меньше
шаблонного кода по сравнению с реализацией объекта класса
<classname>Iterator</classname>, и этот код гораздо более простой и понятный.
Например, эта функция и класс делают одно и то же.
</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);
}
// Против...
class LineIterator implements Iterator
{
protected $fileHandle;
protected $line;
protected $i;
public function __construct($fileName)
{
if (!$this->fileHandle = fopen($fileName, 'r')) {
throw new RuntimeException('Невозможно открыть файл "' . $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>
Однако за эту гибкость приходится платить: генераторы —
однонаправленные итераторы и их нельзя перемотать после начала
итерации. Это также означает, что один и тот же генератор нельзя повторять
несколько раз: генератор необходимо пересоздавать каждый раз, снова вызвав функцию генератора.
</para>
<simplesect role="seealso">
&reftitle.seealso;
<para>
<simplelist>
<member><link linkend="language.oop5.iterations">Итераторы объектов</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
-->