mirror of
https://github.com/php/doc-ru.git
synced 2026-03-23 23:32:16 +01:00
643 lines
20 KiB
XML
643 lines
20 KiB
XML
<?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>:
|
||
добавлением амперсанда (&) к имени функции.
|
||
</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
|
||
-->
|