2024年PHP开发者大会日本站

生成器概述

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

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

生成器提供了一种便捷的方式,可以向foreach循环提供数据,而无需提前在内存中构建数组,这可能会导致程序超出内存限制或需要大量的处理时间来生成。相反,可以使用生成器函数,它与普通的函数相同,不同之处在于,生成器不是只返回一次,而是可以根据需要yield多次,以提供要迭代的值。与迭代器一样,随机数据访问也是不可能的。

一个简单的例子是将range()函数重新实现为一个生成器。标准的range()函数必须生成包含每个值的数组并返回它,这可能会导致大型数组:例如,调用range(0, 1000000) 将导致使用超过 100 MB 的内存。

作为替代方案,我们可以实现一个xrange()生成器,它只需要足够的内存来创建一个Iterator对象并在内部跟踪生成器的当前状态,结果小于 1 KB。

示例 #1 将range()实现为生成器

<?php
function xrange($start, $limit, $step = 1) {
if (
$start <= $limit) {
if (
$step <= 0) {
throw new
LogicException('Step must be positive');
}

for (
$i = $start; $i <= $limit; $i += $step) {
yield
$i;
}
} else {
if (
$step >= 0) {
throw new
LogicException('Step must be negative');
}

for (
$i = $start; $i >= $limit; $i += $step) {
yield
$i;
}
}
}

/*
* 注意,range() 和 xrange() 在下面的输出中结果相同。
*/

echo 'Single digit odd numbers from range(): ';
foreach (
range(1, 9, 2) as $number) {
echo
"$number ";
}
echo
"\n";

echo
'Single digit odd numbers from xrange(): ';
foreach (
xrange(1, 9, 2) as $number) {
echo
"$number ";
}
?>

以上示例将输出

Single digit odd numbers from range():  1 3 5 7 9
Single digit odd numbers from xrange(): 1 3 5 7 9

Generator对象

当调用生成器函数时,将返回内部Generator类的新对象。此对象与仅向前迭代器对象类似地实现Iterator接口,并提供可调用的方法来操作生成器的状态,包括向其发送值和从中返回值。

添加备注

用户贡献备注 6条备注

bloodjazman at gmail dot com
11年前
为了防止资源泄漏
请参阅RFC https://wiki.php.net/rfc/generators#closing_a_generator

并使用finally

示例代码

function getLines($file) {
$f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}

foreach (getLines("file.txt") as $n => $line) {
if ($n > 5) break;
echo $line;
}
montoriusz at gmail dot com
8年前
请记住,生成器函数的执行将推迟到对其结果(Generator 对象)的迭代开始时。如果生成器的结果被赋值给变量而不是立即迭代,这可能会造成混淆。

<?php

$some_state
= 'initial';

function
gen() {
global
$some_state;

echo
"gen() execution start\n";
$some_state = "changed";

yield
1;
yield
2;
}

function
peek_state() {
global
$some_state;
echo
"\$some_state = $some_state\n";
}

echo
"calling gen()...\n";
$result = gen();
echo
"gen() was called\n";

peek_state();

echo
"iterating...\n";
foreach (
$result as $val) {
echo
"iteration: $val\n";
peek_state();
}

?>

如果您需要在调用函数并且在使用结果之前执行某些操作,则必须将生成器包装在另一个函数中。

<?php
/**
* @return Generator
*/
function some_generator() {
global
$some_state;

$some_state = "changed";
return
gen();
}
?>
[email protected]
5年前
补充“[email protected]”的说明: https://php.net/manual/en/language.generators.overview.php#119275

“如果需要在函数被调用以及结果被使用之前执行某些操作,则必须将生成器包装在另一个函数中。”
您可以使用 Generator::rewind 代替 (https://php.net/manual/en/generator.rewind.php)

示例代码
<?php
/** 函数/生成器定义 **/

echo "调用 gen()...\n";
$result = gen();
$result->rewind();
echo
"gen() 已被调用\n";

/** 迭代 **/
?>
[email protected]
8年前
以下是检测循环中断以及如何在中断后进行处理或清理的方法。

<?php
function generator()
{
$complete = false;
try {

while ((
$result = some_function())) {
yield
$result;
}
$complete = true;

} finally {
if (!
$complete) {
// 循环中断时的清理
} else {
// 循环完成时的清理
}
}

// 仅在循环完成后执行某些操作
}
?>
lubaev
10年前
抽象测试。
<?php

$start_time
=microtime(true);
$array = array();
$result = '';
for(
$count=1000000; $count--;)
{
$array[]=$count/2;
}
foreach(
$array as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"时间: ", bcsub($end_time, $start_time, 4), "\n";
echo
"内存 (字节): ", memory_get_peak_usage(true), "\n";

?>

<?php

$start_time
=microtime(true);
$result = '';
function
it()
{
for(
$count=1000000; $count--;)
{
yield
$count/2;
}
}
foreach(
it() as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"时间: ", bcsub($end_time, $start_time, 4), "\n";
echo
"内存 (字节): ", memory_get_peak_usage(true), "\n";

?>
结果
----------------------------------
| 时间 | 内存,MB |
----------------------------------
| 非生成器 | 2.1216 | 89.25 |
|---------------------------------
| 使用生成器 | 6.1963 | 8.75 |
|---------------------------------
| 差异 | < 192% | > 90% |
----------------------------------
[email protected]
10年前
相同的示例,不同的结果

----------------------------------
| 时间 | 内存,MB |
----------------------------------
| 非生成器 | 0.7589 | 146.75 |
|---------------------------------
| 使用生成器 | 0.7469 | 8.75 |
|---------------------------------

两个示例的结果时间在 6.5 到 7.8 之间变化。
因此,在处理速度方面没有真正的缺点。
To Top