287

私は最近、このコードに出くわしました:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

yieldこのキーワードは初めて見ました。取得したコードを実行しようとしています

解析エラー: 構文エラー、予期しない T_VARIABLE 行 x

では、このyieldキーワードは何ですか?それは有効なPHPですか?もしそうなら、どうやってそれを使うのですか?

4

9 に答える 9

439

とはyield?

yieldキーワードは、ジェネレーター関数からデータを返します

ジェネレーター関数の核心は yield キーワードです。最も単純な形式では、yield ステートメントは return ステートメントによく似ていますが、関数の実行を停止して戻るのではなく、yield がジェネレーターをループするコードに値を提供し、ジェネレーター関数の実行を一時停止する点が異なります。

ジェネレータ関数とは何ですか?

ジェネレーター関数は、事実上、 Iteratorを記述するよりコンパクトで効率的な方法です。ループしている間に値を計算して返す関数 ( your xrange)を定義できます。

function xrange($min, $max) {
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

[…]

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}

これにより、次の出力が作成されます。

0 => 1
1 => 2
…
9 => 10

を使用して を$key制御することもできますforeach

yield $someKey => $someValue;

ジェネレーター関数で$someKeyは、 は表示したいもので$keyあり$someValue、 の値です$val。質問の例では、$i.

通常の機能との違いは何ですか?

rangeここで、PHP のネイティブ関数を単純に使用してその出力を実現しない理由を不思議に思うかもしれません。そして、あなたはそうです。出力は同じになります。違いは、そこにたどり着いた方法です。

rangePHPを使用すると、それが実行され、メモリ内に数値の配列全体が作成され、returnその配列全体foreachループに渡されて、値が出力されます。つまり、foreachは配列自体に作用します。そのrange機能とは、foreach一度だけ「話す」こと。郵便で小包を受け取るようなものだと考えてください。配達員が荷物を渡して出発します。そして、パッケージ全体を開いて、そこにあるものをすべて取り出します。

ジェネレーター関数を使用すると、PHP は関数にステップ インし、最後またはyieldキーワードに到達するまで実行します。を満たすと、yieldその時点での値を外側のループに返します。次に、ジェネレーター関数に戻り、生成された場所から続行します。あなたはループをxrange保持しているので、到達forするまで実行して譲歩します。と発電機がピンポンをし$maxているようなものだと考えてください。foreach

なぜそれが必要なのですか?

明らかに、ジェネレーターを使用してメモリ制限を回避できます。環境によっては、 a を実行するrange(1, 1000000)とスクリプトが致命的になりますが、ジェネレーターを使用しても問題なく動作します。またはウィキペディアが言うように:

ジェネレーターは生成された値をオンデマンドでのみ計算するため、一度に計算するのが高価または不可能なシーケンスを表すのに役立ちます。これらには、無限シーケンスやライブ データ ストリームなどがあります。

ジェネレーターもかなり高速であると想定されています。ただし、高速について話しているときは、通常、非常に少数で話していることに注意してください。したがって、実行してジェネレーターを使用するようにすべてのコードを変更する前に、ベンチマークを実行して、それが意味のある場所を確認してください。

ジェネレーターのもう 1 つのユース ケースは、非同期コルーチンです。yieldキーワードは値を返すだけでなく、値を受け入れます。詳細については、以下にリンクされている 2 つの優れたブログ記事を参照してください。

いつから使えyieldますか?

ジェネレーターはPHP 5.5で導入されました。そのバージョンより前のバージョンを使用しようとするとyield、キーワードに続くコードに応じて、さまざまな解析エラーが発生します。したがって、そのコードから解析エラーが発生した場合は、PHP を更新してください。

ソースと参考資料:

于 2013-07-05T07:53:07.623 に答える
26

簡単な例

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

出力

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

高度な例

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

出力

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#
于 2016-09-28T12:29:06.820 に答える
23

yieldキーワードは、PHP 5.5 の「ジェネレーター」の定義に使用されます。では、ジェネレーターとは何ですか?

php.netから:

ジェネレーターは、Iterator インターフェースを実装するクラスを実装する際のオーバーヘッドや複雑さなしに、単純な反復子を実装する簡単な方法を提供します。

ジェネレーターを使用すると、foreach を使用して一連のデータを反復処理するコードを記述できます。配列をメモリ内に構築する必要はありません。これにより、メモリの制限を超えたり、生成にかなりの処理時間が必要になったりする可能性があります。代わりに、通常の関数と同じジェネレーター関数を作成できますが、ジェネレーターは、1 回戻る代わりに、反復される値を提供するために必要な回数だけ生成することができます。

ここから: generators = generators, other functions (単なる単純な関数) = functions.

そのため、次の場合に役立ちます。

  • シンプルなこと(またはシンプルなこと)を行う必要があります。

    generator は、 Iterator インターフェイスを実装するよりもはるかに簡単です。一方で、もちろん、ジェネレーターはあまり機能的ではありません。それらを比較してください

  • 大量のデータを生成する必要があります-メモリを節約します。

    実際にメモリを節約するために、ループの反復ごとに関数を介して必要なデータを生成し、反復後にガベージを利用することができます。ここでの主なポイントは、明確なコードとおそらくパフォーマンスです。あなたのニーズに何が良いかを見てください。

  • 中間値に依存するシーケンスを生成する必要があります。

    これは以前の考えの拡張です。ジェネレーターは、関数と比較して物事を簡単にすることができます。Fibonacci exampleを確認し、ジェネレーターなしでシーケンスを作成してみてください。また、少なくともローカル変数に中間値を格納するため、この場合、ジェネレーターはより高速に動作します。

  • パフォーマンスを向上させる必要があります。

    場合によっては、機能よりも高速に動作する可能性があります (以前の利点を参照)。

于 2013-07-05T12:10:31.647 に答える
21

回答のどれも、数値以外のメンバーが入力された大規模な配列を使用した具体的な例を示していません。explode()これは、大きな .txt ファイル (私の使用例では 262MB) で生成された配列を使用した例です。

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

出力は次のとおりです。

Starting memory usage: 415160
Final memory usage: 270948256

yield次のキーワードを使用して、これを同様のスクリプトと比較します。

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

このスクリプトの出力は次のとおりです。

Starting memory usage: 415152
Final memory usage: 415616

明らかに、メモリ使用量の節約はかなりのものでした (ΔMemoryUsage ----->最初の例では~270.5 MB 、2 番目の例では~450B )。

于 2020-05-14T15:31:16.387 に答える
0

PHP IteratorAggregateインターフェースを実装する場合、yieldキーワードが役立ちます。ArrayIteratorドキュメントを確認してください。またはを使用した例がいくつかありますyield

別の例がphp-ds/polyfillリポジトリにあります: https://github.com/php-ds/polyfill/blob/e52796c50aac6e6cfa6a0e8182943027bacbe187/src/Traits/GenericSequence.php#L359

アイデアは、以下の簡単な例に似ています。

class Collection implements \IteratorAggregate
{
    private $array = [];

    public function push(...$values)
    {
        array_push($this->array, ...$values);
    }

    public function getIterator()
    {
        foreach ($this->array as $value) {
            yield $value;
        }
    }
}

$collection = new Collection();
$collection->push('apple', 'orange', 'banana');

foreach ($collection as $key => $value) {
    echo sprintf("[%s] => %s\n", $key, $value);
}

出力:

[0] => apple
[1] => orange
[2] => banana
于 2021-12-16T04:28:36.447 に答える