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
What does yield mean in PHP?
提问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 yield
keyword 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 yield
keyword? Is it even valid PHP? And if it is, how do I use it?
那么这个yield
关键词是什么呢?它甚至是有效的 PHP 吗?如果是,我该如何使用它?
回答by Gordon
What is yield
?
什么是yield
?
The yield
keyword 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 $key
in the foreach
by using
您也可以控制$key
在foreach
使用
yield $someKey => $someValue;
In the generator function, $someKey
is whatever you want appear for $key
and $someValue
being 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 range
functionto 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 range
PHP, will execute it, create the entire array of numbers in memory and return
that entire arrayto the foreach
loop which will then go over it and output the values. In other words, the foreach
will operate on the array itself. The range
function and the foreach
only "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.
当我们使用range
PHP,将执行它,在内存中创建一个数字的整个阵列,并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 yield
keyword. 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 xrange
holds a for
loop, it will execute and yield until $max
was reached. Think of it like the foreach
and 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 yield
keyword 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 yield
before 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:
来源和进一步阅读:
- Official docs
- The original RFC
- kelunik's blog: An introduction to generators
- ircmaxell's blog: What generators can do for you
- NikiC's blog: Cooperative multitasking using coroutines in PHP
- Co-operative PHP Multitasking
- What is the difference between a generator and an array?
- Wikipedia on Generators in general
回答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
yield
keyword 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 yield
you 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 yield
to 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 foreach
loop changes the $from
variable within the generator. This is because $from
is yielded by referencedue to the ampersand before the generator name. Because of that, the $value
variable within the foreach
loop is a reference to the $from
variable 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 yield
keyword:
现在使用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;
}