生成器概述

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

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

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

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

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

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

<?php
function xrange($start, $limit, $step = 1) {
if (
$start <= $limit) {
if (
$step <= 0) {
throw new
LogicException('步长必须为正数');
}

for (
$i = $start; $i <= $limit; $i += $step) {
yield
$i;
}
} else {
if (
$step >= 0) {
throw new
LogicException('步长必须为负数');
}

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

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

echo '从 range() 获取的个位数奇数: ';
foreach (
range(1, 9, 2) as $number) {
echo
"$number ";
}
echo
"\n";

echo
'从 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 接口,与仅向前迭代器对象的方式非常类似,并提供可以调用来操作生成器状态的方法,包括向其发送值和从中返回值。

添加注释

用户贡献的注释 8 个注释

bloodjazman at gmail dot com
10 年前
为了防止资源泄漏
查看 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() 执行开始\n";
$some_state = "changed";

yield
1;
yield
2;
}

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

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

peek_state();

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

?>

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

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

$some_state = "changed";
return
gen();
}
?>
chung1905 at gmail dot com
4 年前
除了“montoriusz at gmail dot com”的注释之外: 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";

/** 迭代 **/
?>
info at boukeversteegh dot nl
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% |
----------------------------------
dc at libertyskull dot com
10 年前
相同的示例,不同的结果

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

两个示例的结果时间在 6.5 到 7.8 之间变化。
因此,在处理速度方面没有真正的缺点。
youssefbenhssaien at gmail dot com
7 年前
一个用于解析 ini 配置文件的简单函数
<?php
function parse_ini($file_path){
if(!
file_exists($file_path)){
throw new
Exception("文件不存在 ${file_path}");
}
$text = fopen($file_path, 'r');
while(
$line=fgets($text)){
list(
$key, $param) = explode('=', $line);
yield
$key => $param;
}
}
?>
//用法 : parse_ini('param.ini') // 返回生成器对象
//用法 : iterator_to_array(parse_ini('param.ini')); // 返回数组
匿名
5 年前
相同的示例,不同的结果

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

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