PHP の学習 - ジェネレーター
(PHP 5 >= 5.5.0, PHP 7)
ジェネレーターは、より簡単な実装方法を提供します。 Iterator インターフェイスを実装するクラスを定義する場合と比較して、パフォーマンスのオーバーヘッドと複雑さが大幅に軽減されます。
ジェネレーターを使用すると、メモリ内に配列を作成せずに、foreach ブロック内にコードを記述してデータ セットを反復処理できます。これにより、メモリ制限に達したり、かなりの処理時間が必要になります。代わりに、通常のカスタム関数と同じようにジェネレーター関数を作成できます。通常の関数が 1 回だけ返すのではなく、ジェネレーターは反復する必要がある値を生成するために必要なだけ何度でも生成できます。
簡単な例は、ジェネレーターを使用して range() 関数を再実装することです。 標準の range() 関数は、範囲内のすべての値を含む配列をメモリ内に生成し、その配列を返す必要があるため、複数の大きな配列が生成されます。 たとえば、range(0, 1000000) を呼び出すと、メモリ使用量が 100 MB を超えます。
代わりに、xrange() ジェネレーターを実装できます。これは、Iterator オブジェクトを作成するのに十分なメモリのみを必要とし、内部的に保持します。必要なメモリが 1K バイト未満になるように、ジェネレータの現在の状態を追跡します。
例 1 range() をジェネレーターとして実装する
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span> xrange(<span style="color: #800080;">$start</span>, <span style="color: #800080;">$limit</span>, <span style="color: #800080;">$step</span> = 1<span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">$start</span> < <span style="color: #800080;">$limit</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">$step</span> <= 0<span style="color: #000000;">) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> LogicException('Step must be +ve'<span style="color: #000000;">); } </span><span style="color: #0000ff;">for</span> (<span style="color: #800080;">$i</span> = <span style="color: #800080;">$start</span>; <span style="color: #800080;">$i</span> <= <span style="color: #800080;">$limit</span>; <span style="color: #800080;">$i</span> += <span style="color: #800080;">$step</span><span style="color: #000000;">) { yield </span><span style="color: #800080;">$i</span><span style="color: #000000;">; } } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">$step</span> >= 0<span style="color: #000000;">) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> LogicException('Step must be -ve'<span style="color: #000000;">); } </span><span style="color: #0000ff;">for</span> (<span style="color: #800080;">$i</span> = <span style="color: #800080;">$start</span>; <span style="color: #800080;">$i</span> >= <span style="color: #800080;">$limit</span>; <span style="color: #800080;">$i</span> += <span style="color: #800080;">$step</span><span style="color: #000000;">) { yield </span><span style="color: #800080;">$i</span><span style="color: #000000;">; } }}</span><span style="color: #008000;">/*</span><span style="color: #008000;"> * 注意下面range()和xrange()输出的结果是一样的。 </span><span style="color: #008000;">*/</span><span style="color: #0000ff;">echo</span> 'Single digit odd numbers from range(): '<span style="color: #000000;">;</span><span style="color: #0000ff;">foreach</span> (<span style="color: #008080;">range</span>(1, 9, 2) <span style="color: #0000ff;">as</span> <span style="color: #800080;">$number</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$number</span> "<span style="color: #000000;">;}</span><span style="color: #0000ff;">echo</span> "\n"<span style="color: #000000;">;</span><span style="color: #0000ff;">echo</span> 'Single digit odd numbers from xrange(): '<span style="color: #000000;">;</span><span style="color: #0000ff;">foreach</span> (xrange(1, 9, 2) <span style="color: #0000ff;">as</span> <span style="color: #800080;">$number</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$number</span> "<span style="color: #000000;">;}</span>?>
上記のルーチンは次のように出力します:
Single digit odd numbers from range(): <span style="color: #800080;">1</span> <span style="color: #800080;">3</span> <span style="color: #800080;">5</span> <span style="color: #800080;">7</span> <span style="color: #800080;">9</span><span style="color: #000000;"> Single digit odd numbers from xrange(): </span><span style="color: #800080;">1</span> <span style="color: #800080;">3</span> <span style="color: #800080;">5</span> <span style="color: #800080;">7</span> <span style="color: #800080;">9</span>
ジェネレーター関数が初めて呼び出されるとき、内部 Generator クラスのオブジェクトが返されます。このオブジェクトは Iterator インターフェイスを実装します。前方専用イテレータ オブジェクトとほぼ同じ方法で、ジェネレータへの値の送信や値の戻りなど、ジェネレータの状態を操作するために呼び出すことができるメソッドを提供します。
ジェネレーター関数は通常の関数に似ていますが、違いは、通常の関数が値を返すのに対し、ジェネレーターは多くの値を生成できることです。必要な値です。
ジェネレーターが呼び出されると、オブジェクトを反復処理すると (foreach ループなどを介して)、PHP は値が必要になるたびにジェネレーター関数を呼び出します。値を生成した後にジェネレーターの状態を保存し、次の値を生成する必要があるときに呼び出し元の状態を復元できるようにします。
値を生成する必要がなくなったら、ジェネレーター関数は単に終了することができ、ジェネレーターを呼び出すコードは、配列が反復処理されたかのように実行を継続できます。
注:
ジェネレーターは値を返すことができません。値を返すとコンパイル エラーが発生します。ただし、return null は有効な構文であり、ジェネレータを終了して実行を継続します。
ジェネレーター関数の中核は yield キーワードです。最も単純な形式では return ステートメントのように見えますが、通常の return は値を返して関数の実行を終了するのに対し、yield はジェネレーターをループして単に実行を一時停止するコードに値を返すという点が異なります。ジェネレーター機能。
例 #1 値を生成する簡単な例
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> gen_one_to_three() { </span><span style="color: #0000ff;">for</span> (<span style="color: #800080;">$i</span> = 1; <span style="color: #800080;">$i</span> <= 3; <span style="color: #800080;">$i</span>++<span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">注意变量$i的值在不同的yield之间是保持传递的。</span> yield <span style="color: #800080;">$i</span><span style="color: #000000;">; }}</span><span style="color: #800080;">$generator</span> =<span style="color: #000000;"> gen_one_to_three();</span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$generator</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$value</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$value</span>\n"<span style="color: #000000;">;}</span>?>
上記のルーチンは出力します:
<span style="color: #800080;">1</span><span style="color: #800080;">2</span><span style="color: #800080;">3</span>
注:
は、非連想配列と同様に、生成された値を連続する整数インデックスと内部的にペアにします。
式のコンテキスト (代入式の右側など) で yield を使用する場合は、かっこを使用して yield 宣言を囲む必要があります。 たとえば、これは有効です:
<span style="color: #800080;">$data</span> = (yield <span style="color: #800080;">$value</span>);
而这样就不合法,并且在PHP5中会产生一个编译错误:
<span style="color: #800080;">$data</span> = yield <span style="color: #800080;">$value</span>;
The parenthetical restrictions do not apply in PHP 7.
这个语法可以和生成器对象的Generator::send()方法配合使用。
PHP的数组支持关联键值对数组,生成器也一样支持。所以除了生成简单的值,你也可以在生成值的时候指定键名。
如下所示,生成一个键值对与定义一个关联数组十分相似。
Example #2 生成一个键值对
<?<span style="color: #000000;">php</span><span style="color: #008000;">/*</span><span style="color: #008000;"> * 下面每一行是用分号分割的字段组合,第一个字段将被用作键名。 </span><span style="color: #008000;">*/</span><span style="color: #800080;">$input</span> = <<<'EOF'1<span style="color: #000000;">;PHP;Likes dollar signs</span>2<span style="color: #000000;">;Python;Likes whitespace</span>3<span style="color: #000000;">;Ruby;Likes blocksEOF;</span><span style="color: #0000ff;">function</span> input_parser(<span style="color: #800080;">$input</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">foreach</span> (<span style="color: #008080;">explode</span>("\n", <span style="color: #800080;">$input</span>) <span style="color: #0000ff;">as</span> <span style="color: #800080;">$line</span><span style="color: #000000;">) { </span><span style="color: #800080;">$fields</span> = <span style="color: #008080;">explode</span>(';', <span style="color: #800080;">$line</span><span style="color: #000000;">); </span><span style="color: #800080;">$id</span> = <span style="color: #008080;">array_shift</span>(<span style="color: #800080;">$fields</span><span style="color: #000000;">); yield </span><span style="color: #800080;">$id</span> => <span style="color: #800080;">$fields</span><span style="color: #000000;">; }}</span><span style="color: #0000ff;">foreach</span> (input_parser(<span style="color: #800080;">$input</span>) <span style="color: #0000ff;">as</span> <span style="color: #800080;">$id</span> => <span style="color: #800080;">$fields</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$id</span>:\n"<span style="color: #000000;">; </span><span style="color: #0000ff;">echo</span> " <span style="color: #800080;">$fields</span>[0]\n"<span style="color: #000000;">; </span><span style="color: #0000ff;">echo</span> " <span style="color: #800080;">$fields</span>[1]\n"<span style="color: #000000;">;}</span>?>
以上例程会输出:
<span style="color: #800080;">1</span><span style="color: #000000;">: PHP Likes dollar signs</span><span style="color: #800080;">2</span><span style="color: #000000;">: Python Likes whitespace</span><span style="color: #800080;">3</span><span style="color: #000000;">: Ruby Likes blocks</span>
和之前生成简单值类型一样,在一个表达式上下文中生成键值对也需要使用圆括号进行包围:
<span style="color: #800080;">$data</span> = (yield <span style="color: #800080;">$key</span> => <span style="color: #800080;">$value</span>);
Yield可以在没有参数传入的情况下被调用来生成一个 NULL
值并配对一个自动的键名。
Example #3 生成NULL
s
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> gen_three_nulls() { </span><span style="color: #0000ff;">foreach</span> (<span style="color: #008080;">range</span>(1, 3) <span style="color: #0000ff;">as</span> <span style="color: #800080;">$i</span><span style="color: #000000;">) { yield; }}</span><span style="color: #008080;">var_dump</span><span style="color: #000000;">(iterator_to_array(gen_three_nulls()));</span>?>
以上例程会输出:
array(<span style="color: #800080;">3</span><span style="color: #000000;">) { [</span><span style="color: #800080;">0</span>]=><span style="color: #000000;"> NULL [</span><span style="color: #800080;">1</span>]=><span style="color: #000000;"> NULL [</span><span style="color: #800080;">2</span>]=><span style="color: #000000;"> NULL}</span>
生成函数可以像使用值一样来使用引用生成。这个和returning references from functions(从函数返回一个引用)一样:通过在函数名前面加一个引用符号。
Example #4 使用引用来生成值
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span> &<span style="color: #000000;">gen_reference() { </span><span style="color: #800080;">$value</span> = 3<span style="color: #000000;">; </span><span style="color: #0000ff;">while</span> (<span style="color: #800080;">$value</span> > 0<span style="color: #000000;">) { yield </span><span style="color: #800080;">$value</span><span style="color: #000000;">; }}</span><span style="color: #008000;">/*</span><span style="color: #008000;"> * 我们可以在循环中修改$number的值,而生成器是使用的引用值来生成,所以gen_reference()内部的$value值也会跟着变化。 </span><span style="color: #008000;">*/</span><span style="color: #0000ff;">foreach</span> (gen_reference() <span style="color: #0000ff;">as</span> &<span style="color: #800080;">$number</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> (--<span style="color: #800080;">$number</span>).'... '<span style="color: #000000;">;}</span>?>
以上例程会输出:
<span style="color: #800080;">2</span>... <span style="color: #800080;">1</span>... <span style="color: #800080;">0</span>...
In PHP 7, generator delegation allows you to yield values from another generator, Traversable object, or array by using the yield from keyword. The outer generator will then yield all values from the inner generator, object, or array until that is no longer valid, after which execution will continue in the outer generator.
If a generator is used with yield from, the yield from expression will also return any value returned by the inner generator.
Example #5 Basic use of yield from
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> count_to_ten() { yield </span>1<span style="color: #000000;">; yield </span>2<span style="color: #000000;">; yield from [</span>3, 4<span style="color: #000000;">]; yield from </span><span style="color: #0000ff;">new</span> ArrayIterator([5, 6<span style="color: #000000;">]); yield from seven_eight(); yield </span>9<span style="color: #000000;">; yield </span>10<span style="color: #000000;">;}</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> seven_eight() { yield </span>7<span style="color: #000000;">; yield from eight();}</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> eight() { yield </span>8<span style="color: #000000;">;}</span><span style="color: #0000ff;">foreach</span> (count_to_ten() <span style="color: #0000ff;">as</span> <span style="color: #800080;">$num</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$num</span> "<span style="color: #000000;">;}</span>?>
以上例程会输出:
<span style="color: #800080;">1</span> <span style="color: #800080;">2</span> <span style="color: #800080;">3</span> <span style="color: #800080;">4</span> <span style="color: #800080;">5</span> <span style="color: #800080;">6</span> <span style="color: #800080;">7</span> <span style="color: #800080;">8</span> <span style="color: #800080;">9</span> <span style="color: #800080;">10</span>
Example #6 yield from and return values
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> count_to_ten() { yield </span>1<span style="color: #000000;">; yield </span>2<span style="color: #000000;">; yield from [</span>3, 4<span style="color: #000000;">]; yield from </span><span style="color: #0000ff;">new</span> ArrayIterator([5, 6<span style="color: #000000;">]); yield from seven_eight(); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> yield from nine_ten();}</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> seven_eight() { yield </span>7<span style="color: #000000;">; yield from eight();}</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> eight() { yield </span>8<span style="color: #000000;">;}</span><span style="color: #0000ff;">function</span><span style="color: #000000;"> nine_ten() { yield </span>9<span style="color: #000000;">; </span><span style="color: #0000ff;">return</span> 10<span style="color: #000000;">;}</span><span style="color: #800080;">$gen</span> =<span style="color: #000000;"> count_to_ten();</span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$gen</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$num</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">echo</span> "<span style="color: #800080;">$num</span> "<span style="color: #000000;">;}</span><span style="color: #0000ff;">echo</span> <span style="color: #800080;">$gen</span>-><span style="color: #000000;">getReturn();</span>?>
以上例程会输出:
<span style="color: #800080;">1</span> <span style="color: #800080;">2</span> <span style="color: #800080;">3</span> <span style="color: #800080;">4</span> <span style="color: #800080;">5</span> <span style="color: #800080;">6</span> <span style="color: #800080;">7</span> <span style="color: #800080;">8</span> <span style="color: #800080;">9</span> <span style="color: #800080;">10</span>
The primary advantage of generators is their simplicity. Much less boilerplate code has to be written compared to implementing an Iterator class, and the code is generally much more readable. For example, the following function and class are equivalent:
<?<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span> getLinesFromFile(<span style="color: #800080;">$fileName</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (!<span style="color: #800080;">$fileHandle</span> = <span style="color: #008080;">fopen</span>(<span style="color: #800080;">$fileName</span>, 'r'<span style="color: #000000;">)) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">while</span> (<span style="color: #0000ff;">false</span> !== <span style="color: #800080;">$line</span> = <span style="color: #008080;">fgets</span>(<span style="color: #800080;">$fileHandle</span><span style="color: #000000;">)) { yield </span><span style="color: #800080;">$line</span><span style="color: #000000;">; } </span><span style="color: #008080;">fclose</span>(<span style="color: #800080;">$fileHandle</span><span style="color: #000000;">);}</span><span style="color: #008000;">//</span><span style="color: #008000;"> versus...</span><span style="color: #0000ff;">class</span> LineIterator <span style="color: #0000ff;">implements</span><span style="color: #000000;"> Iterator { </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$fileHandle</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$line</span><span style="color: #000000;">; </span><span style="color: #0000ff;">protected</span> <span style="color: #800080;">$i</span><span style="color: #000000;">; </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> __construct(<span style="color: #800080;">$fileName</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span> (!<span style="color: #800080;">$this</span>->fileHandle = <span style="color: #008080;">fopen</span>(<span style="color: #800080;">$fileName</span>, 'r'<span style="color: #000000;">)) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> RuntimeException('Couldn\'t open file "' . <span style="color: #800080;">$fileName</span> . '"'<span style="color: #000000;">); } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">rewind</span><span style="color: #000000;">() { </span><span style="color: #008080;">fseek</span>(<span style="color: #800080;">$this</span>->fileHandle, 0<span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->line = <span style="color: #008080;">fgets</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">fileHandle); </span><span style="color: #800080;">$this</span>->i = 0<span style="color: #000000;">; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> valid() { </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">false</span> !== <span style="color: #800080;">$this</span>-><span style="color: #000000;">line; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">current</span><span style="color: #000000;">() { </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$this</span>-><span style="color: #000000;">line; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">key</span><span style="color: #000000;">() { </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$this</span>-><span style="color: #000000;">i; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">next</span><span style="color: #000000;">() { </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">false</span> !== <span style="color: #800080;">$this</span>-><span style="color: #000000;">line) { </span><span style="color: #800080;">$this</span>->line = <span style="color: #008080;">fgets</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">fileHandle); </span><span style="color: #800080;">$this</span>->i++<span style="color: #000000;">; } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> __destruct() { </span><span style="color: #008080;">fclose</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">fileHandle); }}</span>?>
この柔軟性には代償が伴います。ジェネレーターは順方向のみの反復子であり、反復が開始されると巻き戻すことはできません。これは、同じジェネレーターを複数回繰り返すことができないことも意味します。ジェネレーター関数を再度呼び出してジェネレーターを再構築するか、clone キーワードを使用してクローンを作成する必要があります。
抜粋:http://php.net/manual/zh/ language.generators.php