19

Python(およびその他)では、関数で「yield」演算子を使用して、大量のデータを段階的に処理できます。PHPでこれを行うための同様の方法は何でしょうか?

たとえば、Pythonで、潜在的に非常に大きなファイルを読み取りたい場合は、各行を一度に1つずつ処理できます(この例は、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で)行っているのは、プライベートインスタンス変数を使用して状態を追跡し、関数が呼び出されるたびにそれに応じて動作することですが、もっと良い方法があるはずです。

4

6 に答える 6

18

https://wiki.php.net/rfc/generatorsにrfcがあり、これはPHP5.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";
于 2012-07-15T20:34:16.587 に答える
12

PHPには、ジェネレーターと呼ばれる直接の同等物があります。

古い(php 5.5以前の回答):

残念ながら、同等の言語はありません。最も簡単な方法は、すでに行っていることを実行するか、インスタンス変数を使用して状態を維持するオブジェクトを作成することです。

ただし、関数をforeachステートメントと組み合わせて使用​​する場合は、SPLイテレーターという適切なオプションがあります。これらは、Pythonジェネレーターと非常によく似たものを実現するために使用できます。

于 2009-07-15T19:25:29.730 に答える
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がコールバック関数に渡され、必要なことを実行できます。

于 2009-07-15T20:34:15.347 に答える
3

@Luizの答えを拡張する-もう1つのクールな方法は、無名関数を使用することです。

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;
于 2011-12-17T18:16:41.803 に答える
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') ) {
  // ...
}

それはほぼ正しいように見えます。申し訳ありませんが、テストしていません。

于 2009-07-15T20:03:59.900 に答える
1

同じ文「yield」がPHP5.5に存在します。

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

于 2013-08-11T10:07:48.397 に答える