我最近偶然发现了这段代码:
function xrange($min, $max) { for ($i = $min; $i <= $max; $i++) { yield $i; } }
我之前从未见过这个 yield 关键字 . 试着运行我得到的代码
yield
解析错误:语法错误,第x行意外T_VARIABLE
那么这个 yield 关键字是什么?它甚至是有效的PHP吗?如果是,我该如何使用它?
yield 关键字returns data from a generator function:
生成器函数的核心是yield关键字 . 在最简单的形式中,yield语句看起来很像return语句,除了不是停止执行函数和返回,而是为代码提供循环生成器的代码并暂停生成器函数的执行 .
生成器函数实际上是一种更紧凑和有效的方式来编写Iterator . 它允许您定义一个函数(您的 xrange ),它将在您looping over it时计算并返回值:
xrange
foreach (xrange(1, 10) as $key => $value) { echo "$key => $value", PHP_EOL; }
这将创建以下输出:
0 => 1 1 => 2 … 9 => 10
您还可以使用控制 foreach 中的 $key
foreach
$key
yield $someKey => $someValue;
在生成器函数中, $someKey 是 $key 出现的任何内容, $val 是 $val 中的值 . 在问题's example that' s $i .
$someKey
$val
$i
现在您可能想知道为什么我们不是简单地使用PHP的原生range function来实现该输出 . 你是对的 . 输出将是相同的 . 不同之处在于我们如何到达那里 .
当我们使用 range PHP时,将执行它,在内存中创建整个数字数组,然后 return 整个数组到 foreach 循环,然后将遍历它并输出值 . 换句话说, foreach 将在阵列本身上运行 . range 函数和 foreach 只有"talk"一次 . 把它想象成在邮件中获取包裹 . 送货员将递给您包裹并离开 . 然后你解开整个包裹,取出那里的东西 .
range
return
当我们使用生成器函数时,PHP将进入函数并执行它,直到它满足结束或 yield 关键字 . 当它遇到 yield 时,它将返回当时的值到外循环 . 然后它返回到生成器函数并从它产生的地方继续 . 由于 xrange 持有一个 for 循环,它将执行并屈服,直到达到 $max . 把它想象成 foreach 和打乒乓球的发电机 .
for
$max
显然,生成器可用于解决内存限制 . 根据您的环境,执行 range(1, 1000000) 会使您的脚本致命,而使用生成器的情况也会正常工作 . 或者像维基百科所说:
range(1, 1000000)
因为生成器仅根据需要计算其产生的值,所以它们对于表示一次性计算昂贵或无法计算的序列非常有用 . 这包括例如无限序列和实时数据流 .
发电机也应该非常快 . 但请记住,当我们谈论快速时,我们通常会谈论的数量非常少 . 因此,在您现在运行并更改所有代码以使用生成器之前,请先执行基准测试以了解它的含义 .
生成器的另一个用例是异步协同程序 . yield 关键字不仅返回值,还接受它们 . 有关详细信息,请参阅下面链接的两篇优秀博文 .
发电机已在 PHP 5.5 中引入 . 尝试在该版本之前使用 yield 将导致各种解析错误,具体取决于关键字后面的代码 . 因此,如果您从该代码中获得解析错误,请更新您的PHP .
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
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#
yield 关键字用于在PHP 5.5中定义"generators" . 好的,那么什么是generator?
来自php.net:
生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现Iterator接口的类的开销或复杂性 . 生成器允许您编写使用foreach迭代一组数据的代码,而无需在内存中构建数组,这可能会导致超出内存限制,或者需要相当长的处理时间来生成 . 相反,您可以编写一个生成器函数,它与普通函数相同,除了不是返回一次,生成器可以生成所需数量的次数,以便提供要迭代的值 .
从这个地方:generators = generator,其他函数(只是一个简单的函数)=函数 .
因此,它们在以下情况下很有用
生成器比实现Iterator接口要简单得多 . 另一方面,发电机的功能较少 . compare them .
实际上为了节省内存,我们可以通过每次循环迭代的函数生成所需的数据,并在迭代后使用垃圾 . 所以这里的要点是 - 清晰的代码和可能的性能 . 看看哪个更适合您的需求 .
这是以前思想的延伸 . 与函数相比,生成器可以使事情变得更容易 . 检查Fibonacci example,并尝试在没有生成器的情况下生成序列 . 在这种情况下,生成器也可以更快地工作,至少是因为将中间值存储在局部变量中;
在某些情况下,它们可以更快地工作(参见上一个好处);
这个函数使用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() 返回generator和 b() 只是一个简单的数组 . 你可以迭代两者 .
a()
b()
此外,第一个不分配完整的阵列,因此对内存要求较低 .
使用 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);
注意:生成器很容易出错,所以在实现之前一定要编写单元测试!注意2:在无限循环中使用生成器就像编写一个具有无限长度的闭包......
5 回答
什么是收益率?
yield
关键字returns data from a generator function:什么是发电机功能?
生成器函数实际上是一种更紧凑和有效的方式来编写Iterator . 它允许您定义一个函数(您的
xrange
),它将在您looping over it时计算并返回值:这将创建以下输出:
您还可以使用控制
foreach
中的$key
在生成器函数中,
$someKey
是$key
出现的任何内容,$val
是$val
中的值 . 在问题's example that' s$i
.与普通功能有什么区别?
现在您可能想知道为什么我们不是简单地使用PHP的原生range function来实现该输出 . 你是对的 . 输出将是相同的 . 不同之处在于我们如何到达那里 .
当我们使用
range
PHP时,将执行它,在内存中创建整个数字数组,然后return
整个数组到foreach
循环,然后将遍历它并输出值 . 换句话说,foreach
将在阵列本身上运行 .range
函数和foreach
只有"talk"一次 . 把它想象成在邮件中获取包裹 . 送货员将递给您包裹并离开 . 然后你解开整个包裹,取出那里的东西 .当我们使用生成器函数时,PHP将进入函数并执行它,直到它满足结束或
yield
关键字 . 当它遇到yield
时,它将返回当时的值到外循环 . 然后它返回到生成器函数并从它产生的地方继续 . 由于xrange
持有一个for
循环,它将执行并屈服,直到达到$max
. 把它想象成foreach
和打乒乓球的发电机 .为什么需要呢?
显然,生成器可用于解决内存限制 . 根据您的环境,执行
range(1, 1000000)
会使您的脚本致命,而使用生成器的情况也会正常工作 . 或者像维基百科所说:发电机也应该非常快 . 但请记住,当我们谈论快速时,我们通常会谈论的数量非常少 . 因此,在您现在运行并更改所有代码以使用生成器之前,请先执行基准测试以了解它的含义 .
生成器的另一个用例是异步协同程序 .
yield
关键字不仅返回值,还接受它们 . 有关详细信息,请参阅下面链接的两篇优秀博文 .从什么时候开始使用收益?
发电机已在 PHP 5.5 中引入 . 尝试在该版本之前使用
yield
将导致各种解析错误,具体取决于关键字后面的代码 . 因此,如果您从该代码中获得解析错误,请更新您的PHP .来源和进一步阅读:
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
simple example
output
yield
关键字用于在PHP 5.5中定义"generators" . 好的,那么什么是generator?来自php.net:
从这个地方:generators = generator,其他函数(只是一个简单的函数)=函数 .
因此,它们在以下情况下很有用
生成器比实现Iterator接口要简单得多 . 另一方面,发电机的功能较少 . compare them .
实际上为了节省内存,我们可以通过每次循环迭代的函数生成所需的数据,并在迭代后使用垃圾 . 所以这里的要点是 - 清晰的代码和可能的性能 . 看看哪个更适合您的需求 .
这是以前思想的延伸 . 与函数相比,生成器可以使事情变得更容易 . 检查Fibonacci example,并尝试在没有生成器的情况下生成序列 . 在这种情况下,生成器也可以更快地工作,至少是因为将中间值存储在局部变量中;
在某些情况下,它们可以更快地工作(参见上一个好处);
这个函数使用yield:
几乎没有这个没有:
只有一个区别是
a()
返回generator和b()
只是一个简单的数组 . 你可以迭代两者 .此外,第一个不分配完整的阵列,因此对内存要求较低 .
使用
yield
,您可以轻松地在单个函数中描述多个任务之间的断点 . 这就是全部,没有什么特别之处 .如果task1和task2高度相关,但您需要在它们之间使用断点来执行其他操作:
处理数据库行之间的
空闲内存
运行其他任务,这些任务提供对下一个任务的依赖,但是通过理解当前代码而无关
执行异步调用并等待结果
等等......
生成器是最好的解决方案,因为您不必将代码拆分为多个闭包或将其与其他代码混合使用,或使用回调等等...您只需使用
yield
添加断点,就可以继续如果你准备好那个断点 .添加没有生成器的断点:
用生成器添加断点
注意:生成器很容易出错,所以在实现之前一定要编写单元测试!注意2:在无限循环中使用生成器就像编写一个具有无限长度的闭包......