PHP中与Python的yield运算符等效的实现

19 投票
6 回答
9455 浏览
提问于 2025-04-15 12:55

在Python(还有其他语言)中,你可以通过在函数里使用'yield'这个操作符,逐步处理大量数据。那么在PHP中,有什么类似的方式呢?

举个例子,假设我想在Python中读取一个可能非常大的文件,我可以像这样逐行处理每一行(这个例子有点简单,其实就是'for line in file_obj'的意思):

def file_lines(fname):
    f = open(fname)
    for line in f:
        yield line
    f.close()

for line in file_lines('somefile'):
    #process the line

我现在在PHP中做的是,使用一个私有的实例变量来跟踪状态,每次调用函数时根据这个状态来执行,但我觉得应该有更好的方法。

6 个回答

11

我在用其他语言(比如PHP)实现之前,都会先用Python做个原型。最后我用回调函数来实现我想用的yield功能。

function doSomething($callback) 
{
    foreach ($something as $someOtherThing) {
        // do some computations that generates $data

        call_user_func($callback, $data);
    }
}

function myCallback($input)
{
    // save $input to DB 
    // log
    // send through a webservice
    // etc.
    var_dump($input);
}


doSomething('myCallback');

这样每个$data都会传递给回调函数,你就可以随意处理它。

18

有一个关于这个问题的提案,可以在这个链接找到:https://wiki.php.net/rfc/generators,可能会在PHP 5.5中被加入。

与此同时,可以看看这个简单的“生成器函数”示例,它是在用户空间实现的。

namespace Functional;

error_reporting(E_ALL|E_STRICT);

const BEFORE = 1;
const NEXT = 2;
const AFTER = 3;
const FORWARD = 4;
const YIELD = 5;

class Generator implements \Iterator {
    private $funcs;
    private $args;
    private $key;
    private $result;

    public function __construct(array $funcs, array $args) {
        $this->funcs = $funcs;
        $this->args = $args;
    }

    public function rewind() {
        $this->key = -1;
        $this->result = call_user_func_array($this->funcs[BEFORE], 
                                             $this->args);
        $this->next();
    }

    public function valid() {
        return $this->result[YIELD] !== false;
    }

    public function current() {
        return $this->result[YIELD];
    }

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

    public function next() {
        $this->result = call_user_func($this->funcs[NEXT], 
                                       $this->result[FORWARD]);
        if ($this->result[YIELD] === false) {
            call_user_func($this->funcs[AFTER], $this->result[FORWARD]);
        }
        ++$this->key;
    }
}

function generator($funcs, $args) {
    return new Generator($funcs, $args);
}

/**
 * A generator function that lazily yields each line in a file.
 */
function get_lines_from_file($file_name) {
    $funcs = array(
        BEFORE => function($file_name) { return array(FORWARD => fopen($file_name, 'r'));   },
        NEXT   => function($fh)        { return array(FORWARD => $fh, YIELD => fgets($fh)); },
        AFTER  => function($fh)        { fclose($fh);                                       },
    );
    return generator($funcs, array($file_name));
}

// Output content of this file with padded linenumbers.
foreach (get_lines_from_file(__FILE__) as $k => $v) {
    echo str_pad($k, 8), $v;
}
echo "\n";

12

PHP有一个直接对应的功能,叫做生成器

旧版(PHP 5.5之前的回答):

不幸的是,没有完全相同的功能。最简单的方法就是继续你现在的做法,或者创建一个对象,利用实例变量来保持状态。

不过,如果你想把这个功能和foreach语句一起使用,还有一个不错的选择:SPL迭代器。它们可以用来实现类似于Python生成器的效果。

撰写回答