我最近偶然发现了这段代码:
function xrange($min, $max) { for ($i = $min; $i <= $max; $i++) { yield $i; } }
我以前从来没有见过这个yield关键字。试着运行我得到的代码解析错误:语法错误,第x行出现意外的T_VARIABLEyield关键字是什么?它是有效的PHP吗?如果是,我该如何使用它?
yield
tcomlyy61#
yield关键字returns data from a generator function:生成器函数的核心是yield关键字。在其最简单的形式中,yield语句看起来很像return语句,除了yield不是停止执行函数并返回,而是向循环生成器的代码提供一个值并暂停执行生成器函数。
生成器函数实际上是一种更紧凑、更有效的编写Iterator的方法。它允许你定义一个函数(你的xrange),当你是looping over it时,它将 * 计算并返回 * 值 *:
xrange
function xrange($min, $max) { for ($i = $min; $i <= $max; $i++) { yield $i; } } […] 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;
在generator函数中,$someKey是您希望为$key显示的任何内容,$someValue是$val中的值。在这个问题的例子中,它是$i。请注意,在内部,顺序整数键与产生的值配对,就像非关联数组一样。我们甚至可以用键设置yield值。
$someKey
$someValue
$val
$i
现在您可能想知道为什么我们不简单地使用PHP的原生range function来实现该输出。你说得对输出将是相同的。区别在于我们是如何到达那里的。当我们使用range PHP时,将执行它,在内存中创建整个数字数组,并将return * 整个数组 * 发送到foreach循环,然后循环将遍历它并输出值。换句话说,foreach将对阵列本身进行操作。range函数和foreach只“交谈”一次。就像收到一个包裹。送货员会把包裹递给你然后离开。然后你打开整个包裹,取出里面的东西。当我们使用generator函数时,PHP将单步执行该函数并执行它,直到它遇到end或yield关键字。当它遇到yield时,它将返回当时的值到外部循环。然后它返回到生成器函数,并从它产生的地方继续。由于xrange包含一个for循环,它将执行并退出,直到到达$max。把它想象成foreach和发电机打乒乓球。
range
return
for
$max
显然,生成器可以用来解决内存限制问题。根据您的环境,执行range(1, 1000000)将导致脚本失败,而使用生成器则可以正常工作。正如Wikipedia所说:因为生成器只在需要时计算其产生的值,所以它们对于表示代价昂贵或不可能一次计算的序列很有用。这些包括例如无限序列和实时数据流。发电机也应该相当快。但请记住,当我们谈论快速时,我们通常谈论的是非常小的数字。因此,在您现在运行并更改所有代码以使用生成器之前,请执行基准测试,看看它在哪里有意义。Generator的另一个用例是异步协同程序。yield关键字不仅返回值,而且还接受它们。有关详细信息,请参阅下面链接的两篇优秀博客文章。
range(1, 1000000)
PHP 5.5中引入了生成器。尝试使用该版本之前的yield将导致各种解析错误,具体取决于关键字后面的代码。因此,如果您从该代码中得到一个解析错误,请更新PHP。
nfzehxib2#
此函数使用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()
ztyzrc3y3#
<?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#
<?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#'; ?>
#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#
nbewdwxp4#
没有一个答案给出了使用非数字成员填充的大规模数组的具体示例。下面是一个使用explode()在一个大的. txt文件(在我的用例中为262MB)上生成的数组的示例:
explode()
<?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();
输出为:
Starting memory usage: 415160 Final memory usage: 270948256
现在将其与类似的脚本进行比较,使用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();
此脚本的输出为:
Starting memory usage: 415152 Final memory usage: 415616
显然,存储器使用节省是相当可观的(在第一示例中Δ MemoryUsage---->〜 270.5MB,在第二示例中**〜 450B**)。
vawmfj5a5#
yield关键字在PHP 5.5中用于定义"generators"。什么是generator?来自www.example.com php.net:生成器提供了一种实现简单迭代器的简单方法,而无需实现实现Iterator接口的类的开销或复杂性。生成器允许您编写使用foreach迭代一组数据的代码,而不需要在内存中构建数组,这可能会导致您超过内存限制,或者需要大量的处理时间来生成。相反,您可以编写一个生成器函数,它与普通函数相同,只是生成器可以根据需要多次产生,而不是返回一次,以便提供要迭代的值。从这个地方:generators = generators,其他函数(只是一个简单的函数)=函数。因此,它们在以下情况下很有用:
generator比实现Iterator接口要简单得多。另一方面,当然,发电机的功能较少。compare them。
实际上,为了节省内存,我们可以通过函数为每次循环迭代生成所需的数据,并在迭代后利用垃圾。所以这里的要点是-清晰的代码和可能的性能。看看什么更适合你的需要。
这是上述思想的延伸。与函数相比,生成器可以使事情变得更容易。检查Fibonacci example,并尝试在没有生成器的情况下生成序列。在这种情况下,生成器也可以更快地工作,至少是因为在局部变量中存储了中间值;
在某些情况下,它们可以比功能更快地工作(参见先前的好处);
xsuvu9jc6#
使用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, ...);
如果task 1和task 2高度相关,但您需要在它们之间设置一个断点来执行其他操作:
那么生成器是最好的解决方案,因为你不需要将你的代码分成许多闭包,或者与其他代码混合,或者使用回调等等。您只需使用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);
emeijp437#
一个有趣的方面,值得在这里讨论的是通过引用产生。每次我们需要改变一个参数,使它被反射到函数之外,我们必须通过引用传递这个参数。要将其应用于生成器,我们只需在生成器的名称和迭代中使用的变量前添加一个&符号&:
&
<?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...
上面的示例显示了在foreach循环中更改迭代值如何更改生成器中的$from变量。这是因为$from是通过引用产生的,这是由于生成器名称前的&符号。因此,foreach循环中的$value变量是对生成器函数中$from变量的引用。
$from
$value
khbbv19g8#
下面的代码演示了如何使用生成器在完成之前返回结果,这与传统的非生成器方法不同,后者在完整迭代之后返回完整的数组。使用下面的生成器,当准备好时返回值,无需等待数组完全填充:
<?php function sleepiterate($length) { for ($i=0; $i < $length; $i++) { sleep(2); yield $i; } } foreach (sleepiterate(5) as $i) { echo $i, PHP_EOL; }
31moq8wy9#
当实现PHP IteratorAggregate接口时,yield关键字将非常有用。查看文档,有几个使用ArrayIterator或yield的示例。另一个例子可以在php-ds/polyfill repo中找到:https://github.com/php-ds/polyfill/blob/e52796c50aac6e6cfa6a0e8182943027bacbe187/src/Traits/GenericSequence.php#L359这个想法类似于下面的快速示例:
ArrayIterator
php-ds/polyfill
class Collection implements \IteratorAggregate { private $array = []; public function push(...$values) { array_push($this->array, ...$values); } public function getIterator() { foreach ($this->array as $value) { yield $value; } } } $collection = new Collection(); $collection->push('apple', 'orange', 'banana'); foreach ($collection as $key => $value) { echo sprintf("[%s] => %s\n", $key, $value); }
输出:
[0] => apple [1] => orange [2] => banana
9条答案
按热度按时间tcomlyy61#
什么是
yield
?yield
关键字returns data from a generator function:生成器函数的核心是yield关键字。在其最简单的形式中,yield语句看起来很像return语句,除了yield不是停止执行函数并返回,而是向循环生成器的代码提供一个值并暂停执行生成器函数。
什么是generator函数?
生成器函数实际上是一种更紧凑、更有效的编写Iterator的方法。它允许你定义一个函数(你的
xrange
),当你是looping over it时,它将 * 计算并返回 * 值 *:这将创建以下输出:
您还可以使用以下命令控制
foreach
中的$key
在generator函数中,
$someKey
是您希望为$key
显示的任何内容,$someValue
是$val
中的值。在这个问题的例子中,它是$i
。请注意,在内部,顺序整数键与产生的值配对,就像非关联数组一样。我们甚至可以用键设置yield值。
和普通函数有什么区别?
现在您可能想知道为什么我们不简单地使用PHP的原生
range
function来实现该输出。你说得对输出将是相同的。区别在于我们是如何到达那里的。当我们使用
range
PHP时,将执行它,在内存中创建整个数字数组,并将return
* 整个数组 * 发送到foreach
循环,然后循环将遍历它并输出值。换句话说,foreach
将对阵列本身进行操作。range
函数和foreach
只“交谈”一次。就像收到一个包裹。送货员会把包裹递给你然后离开。然后你打开整个包裹,取出里面的东西。当我们使用generator函数时,PHP将单步执行该函数并执行它,直到它遇到end或
yield
关键字。当它遇到yield
时,它将返回当时的值到外部循环。然后它返回到生成器函数,并从它产生的地方继续。由于xrange
包含一个for
循环,它将执行并退出,直到到达$max
。把它想象成foreach
和发电机打乒乓球。为什么我需要它?
显然,生成器可以用来解决内存限制问题。根据您的环境,执行
range(1, 1000000)
将导致脚本失败,而使用生成器则可以正常工作。正如Wikipedia所说:因为生成器只在需要时计算其产生的值,所以它们对于表示代价昂贵或不可能一次计算的序列很有用。这些包括例如无限序列和实时数据流。
发电机也应该相当快。但请记住,当我们谈论快速时,我们通常谈论的是非常小的数字。因此,在您现在运行并更改所有代码以使用生成器之前,请执行基准测试,看看它在哪里有意义。
Generator的另一个用例是异步协同程序。
yield
关键字不仅返回值,而且还接受它们。有关详细信息,请参阅下面链接的两篇优秀博客文章。什么时候可以用
yield
了?PHP 5.5中引入了生成器。尝试使用该版本之前的
yield
将导致各种解析错误,具体取决于关键字后面的代码。因此,如果您从该代码中得到一个解析错误,请更新PHP。来源及延伸阅读:
nfzehxib2#
此函数使用yield:
它几乎与此相同,但没有:
唯一的区别是
a()
返回一个generator,而b()
只是一个简单的数组。您可以在两者上进行迭代。另外,第一个方法不分配完整的数组,因此对内存的需求较少。
ztyzrc3y3#
nbewdwxp4#
没有一个答案给出了使用非数字成员填充的大规模数组的具体示例。下面是一个使用
explode()
在一个大的. txt文件(在我的用例中为262MB)上生成的数组的示例:输出为:
现在将其与类似的脚本进行比较,使用
yield
关键字:此脚本的输出为:
显然,存储器使用节省是相当可观的(在第一示例中Δ MemoryUsage---->〜 270.5MB,在第二示例中**〜 450B**)。
vawmfj5a5#
yield
关键字在PHP 5.5中用于定义"generators"。什么是generator?来自www.example.com php.net:
生成器提供了一种实现简单迭代器的简单方法,而无需实现实现Iterator接口的类的开销或复杂性。
生成器允许您编写使用foreach迭代一组数据的代码,而不需要在内存中构建数组,这可能会导致您超过内存限制,或者需要大量的处理时间来生成。相反,您可以编写一个生成器函数,它与普通函数相同,只是生成器可以根据需要多次产生,而不是返回一次,以便提供要迭代的值。
从这个地方:generators = generators,其他函数(只是一个简单的函数)=函数。
因此,它们在以下情况下很有用:
generator比实现Iterator接口要简单得多。另一方面,当然,发电机的功能较少。compare them。
实际上,为了节省内存,我们可以通过函数为每次循环迭代生成所需的数据,并在迭代后利用垃圾。所以这里的要点是-清晰的代码和可能的性能。看看什么更适合你的需要。
这是上述思想的延伸。与函数相比,生成器可以使事情变得更容易。检查Fibonacci example,并尝试在没有生成器的情况下生成序列。在这种情况下,生成器也可以更快地工作,至少是因为在局部变量中存储了中间值;
在某些情况下,它们可以比功能更快地工作(参见先前的好处);
xsuvu9jc6#
使用
yield
,您可以轻松地在单个函数中描述多个任务之间的断点。就这样,没什么特别的。如果task 1和task 2高度相关,但您需要在它们之间设置一个断点来执行其他操作:
那么生成器是最好的解决方案,因为你不需要将你的代码分成许多闭包,或者与其他代码混合,或者使用回调等等。您只需使用
yield
添加一个断点,如果准备好了,您可以从该断点继续。添加不带生成器的断点:
使用生成器添加断点
emeijp437#
一个有趣的方面,值得在这里讨论的是通过引用产生。每次我们需要改变一个参数,使它被反射到函数之外,我们必须通过引用传递这个参数。要将其应用于生成器,我们只需在生成器的名称和迭代中使用的变量前添加一个&符号
&
:上面的示例显示了在
foreach
循环中更改迭代值如何更改生成器中的$from
变量。这是因为$from
是通过引用产生的,这是由于生成器名称前的&符号。因此,foreach
循环中的$value
变量是对生成器函数中$from
变量的引用。khbbv19g8#
下面的代码演示了如何使用生成器在完成之前返回结果,这与传统的非生成器方法不同,后者在完整迭代之后返回完整的数组。使用下面的生成器,当准备好时返回值,无需等待数组完全填充:
31moq8wy9#
当实现PHP IteratorAggregate接口时,
yield
关键字将非常有用。查看文档,有几个使用ArrayIterator
或yield
的示例。另一个例子可以在
php-ds/polyfill
repo中找到:https://github.com/php-ds/polyfill/blob/e52796c50aac6e6cfa6a0e8182943027bacbe187/src/Traits/GenericSequence.php#L359这个想法类似于下面的快速示例:
输出: