генератор — Что значит урожай в PHP?

Я недавно наткнулся на этот код:

function xrange($min, $max)
{
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}

Я никогда этого не видела yield Ключевое слово перед. Пытаясь запустить код, который я получаю

Ошибка разбора: синтаксическая ошибка, неожиданный T_VARIABLE в строке x

И что это yield ключевое слово? Это даже действительный PHP? И если это так, как я могу его использовать?

186

Решение

Что такое yield?

yield ключевое слово возвращает данные из функции генератора:

Сердцем функции генератора является ключевое слово yield. В своей простейшей форме оператор yield очень похож на оператор return, за исключением того, что вместо остановки выполнения функции и возврата yield вместо этого предоставляет значение для кода, зацикливающегося над генератором, и приостанавливает выполнение функции генератора.

Что такое функция генератора?

Функция генератора — фактически более компактный и эффективный способ написать Итератор. Это позволяет вам определить функцию (ваш xrange) что будет рассчитать и вернуть ценности в то время как ты зацикливаясь на этом:

foreach (xrange(1, 10) as $key => $value) {
echo "$key => $value", PHP_EOL;
}

Это создаст следующий вывод:

0 => 1
1 => 2
…
9 => 10

Вы также можете контролировать $key в foreach используя

yield $someKey => $someValue;

В функции генератора, $someKey это то, что вы хотите появиться для $key а также $someValue быть значением в $val, В примере вопроса это $i,

В чем разница с обычными функциями?

Теперь вы можете задаться вопросом, почему мы не просто используем нативный PHP range функция чтобы достичь этого результата. И ты прав. Вывод будет таким же. Разница в том, как мы туда попали.

Когда мы используем range PHP, выполнит его, создаст весь массив чисел в памяти и return тот весь массив к foreach цикл, который затем пройдет по нему и выведет значения. Другими словами, foreach будет работать на самом массиве. range функция и foreach только «поговорить» один раз. Думайте об этом как о получении посылки по почте. Курьер доставит вам посылку и уйдет. А затем вы разворачиваете весь пакет, вынимая все, что там есть.

Когда мы используем функцию генератора, PHP будет входить в функцию и выполнять ее, пока она не достигнет конца или не yield ключевое слово. Когда он встречает yield, тогда он вернет любое значение в то время во внешний цикл. Затем он возвращается в функцию генератора и продолжает с того места, где он уступил. Так как ваш xrange держит for цикл, он будет выполняться и давать до $max был достигнут Думайте об этом как foreach и генератор, играющий в пинг-понг.

Зачем мне это?

Очевидно, что генераторы могут использоваться для обхода ограничений памяти. В зависимости от вашей среды, делая range(1, 1000000) будет фатальным ваш сценарий, тогда как то же самое с генератором будет просто отлично работать. Или, как говорит Википедия:

Поскольку генераторы вычисляют свои полученные значения только по требованию, они полезны для представления последовательностей, которые было бы дорого или невозможно вычислить сразу. К ним относятся, например, бесконечные последовательности и живые потоки данных.

Генераторы также должны быть довольно быстрыми. Но имейте в виду, что когда мы говорим о быстром, мы обычно говорим в очень небольших количествах. Поэтому, прежде чем вы сейчас запустите и измените весь свой код на использование генераторов, сделайте тест, чтобы увидеть, где это имеет смысл.

Другой вариант использования для генераторов — асинхронные сопрограммы. yield Ключевое слово не только возвращает значения, но и принимает их. Подробнее об этом см. В двух великолепных сообщениях в блогах, ссылки на которые приведены ниже.

С каких пор я могу использовать yield?

Генераторы были введены в PHP 5.5. Пытаясь использовать yield перед этой версией возникнут различные ошибки разбора, в зависимости от кода, который следует за ключевым словом. Поэтому, если вы получили ошибку разбора этого кода, обновите ваш PHP.

Источники и дальнейшее чтение:

273

Другие решения

yield Ключевое слово служит для определения «генераторов» в PHP 5.5.
Хорошо, тогда что это генератор?

С php.net:

Генераторы предоставляют простой способ реализации простых итераторов без дополнительных затрат и сложности реализации класса, реализующего интерфейс итератора.

Генератор позволяет вам писать код, который использует foreach для итерации по набору данных без необходимости создания массива в памяти, что может привести к превышению лимита памяти или значительному времени обработки для генерации. Вместо этого вы можете написать функцию генератора, которая аналогична обычной функции, за исключением того, что вместо однократного возврата генератор может выдавать столько раз, сколько ему нужно, чтобы предоставить значения для повторения.

Отсюда: генераторы = генераторы, другие функции (просто простые функции) = функции.

Итак, они полезны, когда:

  • вам нужно делать простые вещи (или простые вещи);

    генератор действительно намного проще, чем реализация интерфейса Iterator. с другой стороны, конечно, что генераторы менее функциональны. Сравните их.

  • вам нужно генерировать большие объемы данных — экономя память;

    фактически для экономии памяти мы можем просто генерировать необходимые данные с помощью функций для каждой итерации цикла, а после итерации использовать мусор. Итак, основные моменты — понятный код и, возможно, производительность. посмотрим, что лучше для ваших нужд.

  • вам нужно сгенерировать последовательность, которая зависит от промежуточных значений;

    это расширение предыдущей мысли. генераторы могут сделать вещи проще по сравнению с функциями. проверять Пример Фибоначчи, и попробуйте сделать последовательность без генератора. Также генераторы могут работать быстрее в этом случае, по крайней мере, из-за хранения промежуточных значений в локальных переменных;

  • вам нужно улучшить производительность.

    в некоторых случаях они могут работать быстрее, чем функции (см. предыдущее преимущество);

20

простой пример

<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $v)
echo $v.',';
echo '#end main#';
?>

выход

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
17

Эта функция использует yield:

function a($items) {
foreach ($items as $item) {
yield $item + 1;
}
}

почти такой же, как этот без:

function b($items) {
$result = [];
foreach ($items as $item) {
$result[] = $item + 1;
}
return $result;
}

Только с одним отличием a() возвращает генератор а также b() просто простой массив. Вы можете повторить и то и другое.

Кроме того, первый не выделяет полный массив и поэтому требует меньше памяти.

17

С yield Вы можете легко описать точки останова между несколькими задачами в одной функции. Вот и все, в этом нет ничего особенного.

$closure = function ($injected1, $injected2, ...){
$returned = array();
//task1 on $injected1
$returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
//task2 on $injected2
$returned[] = $returned2;
//...
return $returned;
};
$returned = $closure($injected1, $injected2, ...);

Если task1 и task2 тесно связаны, но вам нужна точка останова между ними, чтобы сделать что-то еще:

  • свободная память между строками базы данных
  • запускать другие задачи, которые обеспечивают зависимость от следующей задачи, но которые не связаны с пониманием текущего кода
  • делать асинхронные вызовы и ждать результатов
  • и так далее …

тогда генераторы — лучшее решение, потому что вам не нужно разбивать свой код на множество замыканий или смешивать его с другим кодом, или использовать обратные вызовы и т. д. Вы просто используете yield добавить точку останова, и вы можете продолжить с этой точки останова, если вы готовы.

Добавить точку останова без генераторов:

$closure1 = function ($injected1){
//task1 on $injected1
return $returned1;
};
$closure2 = function ($injected2){
//task2 on $injected2
return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

Добавить точку останова с генераторами

$closure = function (){
$injected1 = yield;
//task1 on $injected1
$injected2 = (yield($returned1));
//task2 on $injected2
$injected3 = (yield($returned2));
//...
yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

примечание: с генераторами легко ошибиться, поэтому всегда пишите модульные тесты, прежде чем применять их!
note2: Использование генераторов в бесконечном цикле похоже на написание замыкания бесконечной длины …

6
По вопросам рекламы [email protected]