比较生成器和 Iterator 对象

生成器的主要优势在于其简单性。与实现 Iterator 类相比,需要编写的样板代码要少得多,而且代码通常更易读。例如,以下函数和类是等效的

<?php
function getLinesFromFile($fileName) {
if (!
$fileHandle = fopen($fileName, 'r')) {
return;
}

while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}

fclose($fileHandle);
}

// versus...

class LineIterator implements Iterator {
protected
$fileHandle;

protected
$line;
protected
$i;

public function
__construct($fileName) {
if (!
$this->fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}

public function
rewind() {
fseek($this->fileHandle, 0);
$this->line = fgets($this->fileHandle);
$this->i = 0;
}

public function
valid() {
return
false !== $this->line;
}

public function
current() {
return
$this->line;
}

public function
key() {
return
$this->i;
}

public function
next() {
if (
false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}

public function
__destruct() {
fclose($this->fileHandle);
}
}
?>

但是,这种灵活性也需要付出代价:生成器是单向迭代器,一旦开始迭代就不能回滚。这也意味着同一个生成器不能多次迭代:需要再次调用生成器函数来重建生成器。

另请参阅

添加备注

用户贡献备注 4 备注

mNOSPAMsenghaa at nospam dot gmail dot com
11 年前
这两个示例之间的大小比较似乎不公平。如上所述,生成器是单向的,这意味着它应该与定义了虚拟回滚函数的迭代器进行比较。此外,为了公平起见,既然迭代器抛出异常,生成器示例是否也应该抛出相同的异常?代码比较将变为如下

<?php
function getLinesFromFile($fileName) {
if (!
$fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Couldn\'t open file "' . $fileName . '"');
}

while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}

fclose($fileHandle);
}

// versus...

class LineIterator implements Iterator {
protected
$fileHandle;

protected
$line;
protected
$i;

public function
__construct($fileName) {
if (!
$this->fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}

public function
rewind() { }

public function
valid() {
return
false !== $this->line;
}

public function
current() {
return
$this->line;
}

public function
key() {
return
$this->i;
}

public function
next() {
if (
false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}

public function
__destruct() {
fclose($this->fileHandle);
}
}
?>

生成器仍然明显短得多,但这似乎是一个更合理的比较。
sergeyzsg at yandex dot ru
10 年前
我认为这是一个糟糕的生成器示例。
如果用户没有消耗所有行,则文件将不会关闭。

<?php
function getLinesFromFile($fileHandle) {
while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}
}

if (
$fileHandle = fopen($fileName, 'r')) {
/*
使用 getLinesFromFile 函数进行操作
*/
fclose($fileHandle);
}
?>
sou at oand dot re
11 年前
我认为为了与函数中的示例更相似,抛出新的异常会更好。但是查看“生成器语法”部分,你可以看到:“在生成器中,空返回语句是有效的语法,它将终止生成器”。从这个角度来看,我们可以想象这仅仅是为了举例说明空返回的使用。
jorgeley at gmail dot com
4 年前
我认为生成器的强大功能在这里被低估了,请看我的示例

<?php

/**
* 简单的示例类,只是为了有一个实例化的对象
*/
class obj {

private
$i = 1;
private
$a = [];

function
__construct($i = 1) {
$this->i = $i;
$this->a = range(0, $i);
}

public function
getI() {
return
$this->i;
}
//更多 getter 和 setter ...
}

/**
* 这是批量返回对象的常用方式
* @param int $n
* @return \obj
*/
function returnObjects($n = 1000) {
$objs = [];
for (
$i = 1; $i <= $n; $i++) {
$objs[] = new obj($i);
}
return
$objs;
}

/**
* 这是使用生成器的更好方法,而不是返回所有对象,
* 它一次返回一个(它在每次调用时保存函数的状态)
* @param int $n
*/
function generateObjects($n = 1000) {
for (
$i = 1; $i <= $n; $i++) {
/**
* 'yield' 返回对象并保存函数状态,所以
* 下次调用从下一个循环迭代开始,依此类推...
*/
yield (new obj($i));
}
}

//主脚本:获取当前内存,运行其中一个函数并计算内存使用情况
$m = memory_get_peak_usage();
/**
* 注释掉下面的 'returnObjects()' 调用,并取消注释 'generateObjects()' 调用
* 如果你想查看生成器的内存使用情况
*/
//$objs = returnObjects();

/**
* 注释掉 'generateObjects()' 并取消注释 'returnObjects()' 调用,如果你
* 想查看普通函数返回的内存使用情况
*/
$objs = generateObjects();
foreach (
$objs as $obj) {
echo
get_class($obj) . ": {$obj->getI()}\n";
}
echo
"总内存消耗: " . (memory_get_peak_usage() - $m) . " 字节\n";

?>

结果是什么?使用 'returnObjects()' 我们返回了一个包含 1000 个对象的数组,但使用 'generateObjects()' 我们只实例化了一个对象,因为 'yield' 返回(停止循环)但也保存了函数的状态,所以下次调用函数会恢复而不是重新开始。在我的环境中,我得到的结果是 37K 到 25M 的差异。
感谢我亲爱的朋友 Ivan Frezza 帮助我更好地理解了这一点!
To Top