ГенераторыЗнакомство с генераторами
Генераторы — легкий способ реализации простых
итераторов
без дополнительных ресурсов или сложностей, которые связаны с написанием класса,
который реализует интерфейс Iterator.
Генератор помогает писать код, который использует &foreach; для перебора
набора данных без необходимости создания массива в памяти, что может привести
к превышению лимита памяти или значительного времени, чтобы
его создать. Вместо этого можно написать функцию-генератор,
которая аналогична обычной
функции, за исключением того, что
вместо возврата
единственного значения генератор умеет возвращать (&yield;) результат столько раз, сколько необходимо
для генерации значений, которые позволяют перебрать исходный набор данных.
Простой пример этого — переопределение функции
range как генератора. Стандартная функция
range генерирует массив, который состоит из значений, и
возвращает его, что приводит к генерации больших массивов: например,
вызов range(0, 1000000) займёт более 100 МБ
оперативной памяти.
В качестве альтернативы можно создать генератор xrange(),
который использует память только чтобы создать объект
Iterator и сохранить текущее состояние, что потребует
не больше 1 килобайта памяти.
Реализация функции range как генератора
= 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 ";
}
?>
]]>
&example.outputs;
Объект Generator
Когда функция-генератор вызывается, она возвращает объект встроенного
класса Generator. Этот объект реализует интерфейс
Iterator, станет однонаправленным объектом итератора
и предоставит методы, с помощью которых можно управлять его состоянием, включая
передачу в него и возвращения из него значений.
Синтаксис генераторов
Функция-генератор выглядит как обычная функция, за исключением того, что
вместо возврата значения генератор выдаёт столько значений, столько
ему необходимо.
Каждая функция с оператором &yield; — функция-генератор.
Когда вызывается генератор, он возвращает объект, который можно итерировать.
При итерации по этому объекту (например, в цикле &foreach;), PHP вызывает
методы итерации объекта каждый раз, когда ему требуется значение, а затем
сохраняет состояние генератора и при следующем вызове возвращает следующее
значение.
Когда значения в генераторе закончатся, генератор может просто выполнить возврат,
и вызывающий код продолжится так же, как если бы в массиве закончились
значения.
Генераторы умеют возвращать значения, которые можно получить
методом Generator::getReturn.
Ключевое слово yield
Сердце функции-генератора — ключевое слово yield.
В простейшей форме инструкция yield похожа на инструкцию
return, за исключением того, что вместо остановки выполнения функции и возврата,
yield отдаёт значение коду, который выполняет цикл над генератором,
и приостанавливает выполнение функции генератора.
Простой пример выдачи значений
]]>
&example.outputs;
Внутренне последовательные целочисленные ключи свяжутся с полученными значениями,
как и в случае с неассоциативным массивом.
Получение значений с ключами
PHP также поддерживает ассоциативные массивы, и генераторы — не исключение.
Помимо получения простых значений, как показывает пример, разрешается также
одновременно получить ключ.
Синтаксис получения пары ключ и значение очень похож на синтаксис определения
ассоциативных массивов, как показывает следующий пример.
Получение пар ключ/значение
$fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
?>
]]>
&example.outputs;
Получение значений null
Чтобы получить значение &null;, нужно вызвать yield без аргументов. Ключ сгенерируется
автоматически.
Получение &null;
]]>
&example.outputs;
NULL
[1]=>
NULL
[2]=>
NULL
}
]]>
Получение значения по ссылке
Функции-генераторы умеют возвращать значения как по ссылке, так и по значению. Это делается так же,
как и возврат ссылок из функций:
добавлением амперсанда (&) к имени функции.
Получение значений по ссылке
0) {
yield $value;
}
}
/* Обратите внимание, что можно изменять значение переменной $number в цикле,
* и поскольку генератор возвращает ссылку, переменная $value
* в функции gen_reference() также изменится. */
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
?>
]]>
&example.outputs;
Делегирование генератора через yield from
Делегирование генератора позволяет получать значения
из другого генератора, объекта Traversable или массива
через ключевые слова yield from.
Внешний генератор будет возвращать значения из внутреннего генератора,
объекта или массива до тех пор, пока они не перестанут действовать, после чего
выполнение продолжится во внешнем генераторе.
Если генератор используется с ключевыми словами yield from, выражение
yield from также будет возвращать значения из
внутреннего генератора.
Сохранение в массив (например, через функцию iterator_to_array)
Ключевые слова yield from не сбрасывают ключи. Ключи, которые вернул
объект Traversable или массив, сохранятся.
Поэтому некоторые значения могут пересекаться по ключам с другими выражениями
yield или yield from, что при записи
в массив перезапишет прежние значения этим ключом.
Распространенный случай, когда это имеет значение, — функция iterator_to_array,
которая возвращает массив с ключом по умолчанию, что иногда приводит
к неожиданным результатам. У функции iterator_to_array есть второй параметр
preserve_keys, которому можно присвоить значение &false;
для генерации собственных ключей и игнорирования ключей,
которые передаются из объекта Generator.
Выражение yield from с функцией iterator_to_array
]]>
&example.outputs;
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
]]>
Основы работы с выражением yield from
]]>
&example.outputs;
Выражение yield from и возвращаемые значения
getReturn();
?>
]]>
&example.outputs;
Сравнение генераторов с объектами класса Iterator
Главное преимущество генераторов — простота. Требуется написать гораздо меньше
шаблонного кода по сравнению с реализацией объекта класса
Iterator, и этот код гораздо более простой и понятный.
Например, эта функция и класс делают одно и то же.
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);
}
}
?>
]]>
Однако за эту гибкость приходится платить: генераторы —
однонаправленные итераторы и их нельзя перемотать после начала
итерации. Это также означает, что один и тот же генератор нельзя повторять
несколько раз: генератор необходимо пересоздавать каждый раз, снова вызвав функцию генератора.
&reftitle.seealso;
Итераторы объектов