【问题标题】:PHP equivalent to Python's yield operatorPHP 相当于 Python 的 yield 运算符
【发布时间】:2009-07-15 19:15:19
【问题描述】:

在 Python(和其他)中,您可以通过在函数中使用“yield”运算符来增量处理大量数据。在 PHP 中这样做的类似方法是什么?

例如,假设在 Python 中,如果我想读取一个可能非常大的文件,我可以像这样一次处理每一行(这个例子是人为的,因为它基本上与 'for 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 中)正在做的是使用私有实例变量来跟踪状态,并在每次调用函数时采取相应的行动,但似乎必须有更好的方法。

【问题讨论】:

标签: php python lazy-evaluation


【解决方案1】:

https://wiki.php.net/rfc/generators 有一个 rfc 解决了这个问题,它可能包含在 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";

【讨论】:

【解决方案2】:

PHP 有一个直接等价物,称为 generators

旧(php 5.5 之前的答案):

很遗憾,没有对应的语言。最简单的方法是按照您已经在做的事情,或者创建一个使用实例变量来维护状态的对象。

但是,如果您想将该函数与 foreach 语句结合使用,则有一个不错的选择:SPL Iterators。它们可用于实现与 python 生成器非常相似的功能。

【讨论】:

  • 嗯,值得一问。
  • 从 PHP 5.5 开始,生成器 包含在 PHP 中。所以你现在可以使用它们了 :)(包括 yield 关键字!)
【解决方案3】:

在用任何其他语言(包括 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都会传递给回调函数,你可以做你想做的事。

【讨论】:

  • 这样你实现了不同于请求的迭代器类型:internal vs external(即foreach)。外部迭代器更强大。
【解决方案4】:

扩展@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.ini 中。不过,我现在可能使用它们太多了:/
【解决方案5】:

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

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') ) {
  // ...
}

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

【讨论】:

  • 这个方案的问题是函数自带状态,只能使用一次。
  • 这并不完全正确。它一次只能与一个文件一起使用。但是,修改它很容易,以便您可以将它与多个文件一起使用。
【解决方案6】:

现在 PHP 5.5 上存在同一个句子 'yield':

http://php.net/manual/en/language.generators.syntax.php

【讨论】:

    猜你喜欢
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    相关资源
    最近更新 更多