php PHP中的yield是什么意思?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17483806/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 12:58:30  来源:igfitidea点击:

What does yield mean in PHP?

phpgeneratorphp-5.5yield-keyword

提问by Gordon

I've recently stumbled over this code:

我最近偶然发现了这段代码:

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

I've never seen this yieldkeyword before. Trying to run the code I get

我以前从未见过这个yield关键字。试图运行我得到的代码

Parse error: syntax error, unexpected T_VARIABLE on line x

解析错误:语法错误,第 x 行出现意外的 T_VARIABLE

So what is this yieldkeyword? Is it even valid PHP? And if it is, how do I use it?

那么这个yield关键词是什么呢?它甚至是有效的 PHP 吗?如果是,我该如何使用它?

回答by Gordon

What is yield?

什么是yield

The yieldkeyword returns data from a generator function:

yield关键字从发电机函数返回数据:

The heart of a generator function is the yield keyword. In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function.

生成器函数的核心是 yield 关键字。在最简单的形式中,yield 语句看起来很像 return 语句,不同之处在于,yield 不是停止执行函数并返回,而是为循环生成器的代码提供一个值并暂停生成器函数的执行。

What is a generator function?

什么是生成器函数?

A generator function is effectively a more compact and efficient way to write an Iterator. It allows you to define a function (your xrange) that will calculate and returnvalues whileyou are looping over it:

生成器函数实际上是编写Iterator的更紧凑和有效的方式。它允许您定义一个函数(您的xrange),该函数将循环计算并返回值:

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

This would create the following output:

这将创建以下输出:

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

You can also control the $keyin the foreachby using

您也可以控制$keyforeach使用

yield $someKey => $someValue;

In the generator function, $someKeyis whatever you want appear for $keyand $someValuebeing the value in $val. In the question's example that's $i.

在生成器函数中,$someKey是您想要出现的任何内容$key$someValue作为$val. 在问题的示例中,$i.

What's the difference to normal functions?

与普通功能有什么区别?

Now you might wonder why we are not simply using PHP's native rangefunctionto achieve that output. And right you are. The output would be the same. The difference is how we got there.

现在您可能想知道为什么我们不简单地使用 PHP 的本机range函数来实现该输出。你是对的。输出将是相同的。不同之处在于我们如何到达那里。

When we use rangePHP, will execute it, create the entire array of numbers in memory and returnthat entire arrayto the foreachloop which will then go over it and output the values. In other words, the foreachwill operate on the array itself. The rangefunction and the foreachonly "talk" once. Think of it like getting a package in the mail. The delivery guy will hand you the package and leave. And then you unwrap the entire package, taking out whatever is in there.

当我们使用rangePHP,将执行它,在内存中创建一个数字的整个阵列,并return认为整个阵列foreach循环,然后将去在它和输出的值。换句话说,foreach将在数组本身上进行操作。该range功能和foreach唯一的“谈话”一次。把它想象成在邮件中收到一个包裹。送货员会把包裹递给你然后离开。然后你打开整个包裹,取出里面的东西。

When we use the generator function, PHP will step into the function and execute it until it either meets the end or a yieldkeyword. When it meets a yield, it will then return whatever is the value at that time to the outer loop. Then it goes back into the generator function and continues from where it yielded. Since your xrangeholds a forloop, it will execute and yield until $maxwas reached. Think of it like the foreachand the generator playing ping pong.

当我们使用生成器函数时,PHP 会步入该函数并执行它,直到它遇到 end 或yield关键字。当它遇到 a 时yield,它会将当时的任何值返回给外部循环。然后它回到生成器函数并从它产生的地方继续。由于您xrange持有一个for循环,它将执行并产生直到$max达到。把它想象成foreach打乒乓球的发电机。

Why do I need that?

为什么我需要那个?

Obviously, generators can be used to work around memory limits. Depending on your environment, doing a range(1, 1000000)will fatal your script whereas the same with a generator will just work fine. Or as Wikipedia puts it:

显然,生成器可用于解决内存限制。根据您的环境,执行一个range(1, 1000000)会使您的脚本致命,而使用生成器执行相同的操作会正常工作。或者如维基百科所说:

Because generators compute their yielded values only on demand, they are useful for representing sequences that would be expensive or impossible to compute at once. These include e.g. infinite sequences and live data streams.

由于生成器仅根据需要计算它们的生成值,因此它们可用于表示一次计算成本高昂或无法计算的序列。这些包括例如无限序列和实时数据流。

Generators are also supposed to be pretty fast. But keep in mind that when we are talking about fast, we are usually talking in very small numbers. So before you now run off and change all your code to use generators, do a benchmark to see where it makes sense.

发电机也应该很快。但请记住,当我们谈论快速时,我们通常谈论的数字非常小。因此,在您现在运行并更改所有代码以使用生成器之前,先做一个基准测试,看看它在哪里有意义。

Another Use Case for Generators is asynchronous coroutines. The yieldkeyword does not only return values but it also accepts them. For details on this, see the two excellent blog posts linked below.

生成器的另一个用例是异步协程。该yield关键字不仅返回值,而且还接受它们。有关这方面的详细信息,请参阅下面链接的两篇优秀博客文章。

Since when can I use yield?

yield什么时候可以使用?

Generators have been introduced in PHP 5.5. Trying to use yieldbefore that version will result in various parse errors, depending on the code that follows the keyword. So if you get a parse error from that code, update your PHP.

PHP 5.5 中引入了生成器。尝试yield在该版本之前使用会导致各种解析错误,具体取决于关键字后面的代码。因此,如果您从该代码中得到解析错误,请更新您的 PHP。

Sources and further reading:

来源和进一步阅读:

回答by tsusanka

This function is using yield:

这个函数使用了yield:

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

is almost the same as this one without:

几乎和这个一样,没有:

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

The only one difference is that a()returns a generatorand b()just a simple array. You can iterate on both.

唯一的区别是a()返回一个生成器b()一个简单的数组。您可以对两者进行迭代。

Also, the first one does not allocate a full array and is therefore less memory-demanding.

此外,第一个不分配完整数组,因此对内存的要求较低。

回答by Think Big

simple example

简单的例子

<?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#';
?>

output

输出

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

advanced example

进阶范例

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

output

输出

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#

回答by QArea

yieldkeyword serves for definition of "generators" in PHP 5.5. Ok, then what is a generator?

yield关键字用于在 PHP 5.5 中定义“生成器”。好的,那么什么是生成器

From php.net:

来自 php.net:

Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.

A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.

生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现 Iterator 接口的类的开销或复杂性。

生成器允许您编写使用 foreach 迭代一组数据的代码,而无需在内存中构建数组,这可能会导致您超出内存限制,或者需要大量的处理时间来生成。相反,您可以编写一个生成器函数,该函数与普通函数相同,除了不是返回一次,生成器可以根据需要多次产生以提供要迭代的值。

From this place: generators = generators, other functions (just a simple functions) = functions.

从这个地方:generators = generators,其他函数(只是一个简单的函数)=函数。

So, they are useful when:

因此,它们在以下情况下很有用:

  • you need to do things simple (or simple things);

    generator is really much simplier then implementing the Iterator interface. other hand is, ofcource, that generators are less functional. compare them.

  • you need to generate BIG amounts of data - saving memory;

    actually to save memory we can just generate needed data via functions for every loop iteration, and after iteration utilize garbage. so here main points is - clear code and probably performance. see what is better for your needs.

  • you need to generate sequence, which depends on intermediate values;

    this is extending of the previous thought. generators can make things easier in comparison with functions. check Fibonacci example, and try to make sequence without generator. Also generators can work faster is this case, at least because of storing intermediate values in local variables;

  • you need to improve performance.

    they can work faster then functions in some cases (see previous benefit);

  • 你需要做简单的事情(或简单的事情);

    generator 确实比实现 Iterator 接口简单得多。另一方面,当然,生成器的功能较差。比较它们

  • 您需要生成大量数据 - 节省内存;

    实际上,为了节省内存,我们可以通过函数为每次循环迭代生成所需的数据,并在迭代后利用垃圾。所以这里的要点是 - 清晰的代码和可能的性能。看看什么更适合您的需求。

  • 您需要生成序列,这取决于中间值;

    这是之前的想法的延伸。与函数相比,生成器可以使事情变得更容易。检查斐波那契示例,并尝试在没有生成器的情况下制作序列。在这种情况下,生成器也可以更快地工作,至少因为将中间值存储在局部变量中;

  • 你需要提高性能。

    在某些情况下,它们可以比功能更快地工作(参见之前的好处);

回答by inf3rno

With yieldyou can easily describe the breakpoints between multiple tasks in a single function. That's all, there is nothing special about it.

有了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, ...);

If task1 and task2 are highly related, but you need a breakpoint between them to do something else:

如果 task1 和 task2 高度相关,但您需要在它们之间设置断点以执行其他操作:

  • free memory between processing database rows
  • run other tasks which provide dependency to the next task, but which are unrelated by understanding the current code
  • doing async calls and wait for the results
  • and so on ...
  • 处理数据库行之间的空闲内存
  • 运行其他任务,这些任务提供对下一个任务的依赖,但与理解当前代码无关
  • 进行异步调用并等待结果
  • 等等 ...

then generators are the best solution, because you don't have to split up your code into many closures or mix it with other code, or use callbacks, etc... You just use yieldto add a breakpoint, and you can continue from that breakpoint if you are ready.

那么生成器是最好的解决方案,因为您不必将您的代码拆分成许多闭包或将其与其他代码混合,或使用回调等......您只需yield添加一个断点,您就可以从那里继续如果你准备好了,断点。

Add breakpoint without generators:

添加不带生成器的断点:

$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);
//...

Add breakpoint with generators

使用生成器添加断点

$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);

note: It is easy to make mistake with generators, so always write unit tests before you implement them!note2: Using generators in an infinite loop is like writing a closure which has infinite length...

注意:使用生成器很容易出错,所以在实现它们之前一定要编写单元测试!注意2:在无限循环中使用生成器就像编写一个无限长度的闭包......

回答by Bud Damyanov

An interesting aspect, which worth to be discussed here, is yielding by reference. Every time we need to change a parameter such that it is reflected outside of the function, we have to pass this parameter by reference. To apply this to generators, we simply prepend an ampersand &to the name of the generator and to the variable used in the iteration:

一个值得在这里讨论的有趣方面是通过引用产生。每次我们需要更改参数以使其反映到函数之外时,我们必须通过引用传递此参数。要将其应用于生成器,我们只需&在生成器的名称和迭代中使用的变量前添加一个&符号:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

The above example shows how changing the iterated values within the foreachloop changes the $fromvariable within the generator. This is because $fromis yielded by referencedue to the ampersand before the generator name. Because of that, the $valuevariable within the foreachloop is a reference to the $fromvariable within the generator function.

上面的例子展示了在foreach循环中改变迭代值如何改变$from生成器中的变量。这是因为生成器名称前的与号$from通过引用产生的。正因为如此,在$value该内的可变foreach环是对基准$from的生成器函数中的变量。

回答by David Partyka

None of the answers above show a concrete example using massive arrays populated by non-numeric members. Here is an example using an array generated by explode()on a large .txt file (262MB in my use case):

上面的答案都没有显示使用由非数字成员填充的大量数组的具体示例。这是一个使用由explode()大型 .txt 文件(在我的用例中为 262MB)上生成的数组的示例:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

The output was:

输出是:

Starting memory usage: 415160
Final memory usage: 270948256

Now compare that to a similar script, using the yieldkeyword:

现在使用yield关键字将其与类似的脚本进行比较:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

The output for this script was:

这个脚本的输出是:

Starting memory usage: 415152
Final memory usage: 415616

Clearly memory usage savings were considerable (ΔMemoryUsage -----> ~270.5 MBin first example, ~450Bin second example).

显然,内存使用节省是相当可观的(ΔMemoryUsage ----->第一个示例中的~270.5 MB,第二个示例中的~450B)。

回答by Risteard

The below code illustrates how using a generator returns a result before completion, unlike the traditional non generator approach that returns a complete array after full iteration. With the generator below, the values are returned when ready, no need to wait for an array to be completely filled:

下面的代码说明了如何使用生成器在完成之前返回结果,这与在完整迭代后返回完整数组的传统非生成器方法不同。使用下面的生成器,准备好时返回值,无需等待数组完全填充:

<?php 

function sleepiterate($length) {
    for ($i=0; $i < $length; $i++) {
        sleep(2);
        yield $i;
    }
}

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}