PHP中与Python的yield操作符相当的语法是什么?

19

在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中)是使用私有实例变量来跟踪状态,并根据每次调用函数进行操作,但似乎必须有更好的方法。


1
这可能不是直接解决您问题的方案,但似乎 PHP 很快就会引入生成器(使用 yield 关键字)了!请参见:https://wiki.php.net/rfc/generators - gpmcadam
是的,PHP 5.5支持yield:http://se2.php.net/manual/en/language.generators.overview.php - Zain Rizvi
6个回答

18

有一个关于此事的rfchttps://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";


3
已包含在5.5 Alpha版本中:https://github.com/php/php-src/blob/php-5.5.0alpha1/NEWS - Sjaak Trekhaak

12

PHP有一个直接等价物叫做生成器

旧版(php 5.5之前的答案):

不幸的是,没有语言等价物。最简单的方法要么是继续你已经在做的事情,要么是创建一个使用实例变量来维护状态的对象。

然而,如果你想要将该函数与foreach语句一起使用,有一个很好的选择:SPL迭代器。它们可以用来实现与Python生成器相似的功能。


哦,好吧,问问也无妨。 - nilamo
2
从PHP 5.5开始,生成器已经包含在PHP中。所以现在你可以使用它们了 :) (包括yield关键字!) - Tularis

11

在使用其他编程语言之前,我会先用Python对所有东西进行原型设计,包括PHP。我最终使用回调函数来实现我本可以使用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传递给回调函数,您可以根据需要进行操作。


这样你就可以实现一个不同于请求的迭代器类型:内部外部(即foreach)。外部迭代器更加强大。 - CapelliC

3

扩展@Luiz的答案 - 另一种很酷的方法是使用匿名函数:

function iterator($n, $cb)
{
    for($i=0; $i<$n; $i++) {
        call_user_func($cb, $i);
    }
}

$sum = 0;
iterator(10,
    function($i) use (&$sum)
    {
        $sum += $i;
    }
);

print $sum;

我很高兴匿名函数被添加到了PHP。不过现在我可能使用它们太多了:/ - nilamo

1

可能没有等效的运算符,但以下代码在功能和开销上是等效的:

function file_lines($file) {
  static $fhandle;

  if ( is_null($fhandle) ) {
    $fhandle = fopen($file, 'r');

    if ( $fhandle === false ) {
      return false;
    }
  }

  if ( ($line = fgets($fhandle))!== false ) {
    return $line;
  }


  fclose($fhandle);
  $fhandle = null;
}

while ( $line = file_lines('some_file') ) {
  // ...
}

看起来差不多。抱歉,我还没有测试过它。


1
这个解决方案的问题在于函数带有状态,并且只能使用一次。 - Arnaud Le Blanc
这并不完全正确。它一次只能用于一个文件。但是,很容易修改它,使其可以用于多个文件。 - Justin Johnson

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接