登录  /  注册

PHP的学习-生成器Generators

php中文网
发布: 2016-06-13 12:28:29
原创
839人浏览过

PHP的学习--生成器Generators

生成器总览

(PHP 5 >= 5.5.0, PHP 7)

生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。

生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

一个简单的例子就是使用生成器来重新实现 range() 函数。 标准的 range() 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。

做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到1K字节的内存。

example #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> $limit<span style="color: #000000;">) {        </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">$step</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> $limit; <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> &gt;= 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> &gt;= <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>?&gt;
登录后复制

以上例程会输出:

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 objects

When a generator function is called for the first time, an object of the internal Generator class is returned. This object implements the Iterator interface in much the same way as a forward-only iterator object would, and provides methods that can be called to manipulate the state of the generator, including sending values to and returning values from it.

 

生成器语法

一个生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成器可以 yield 生成许多它所需要的值。

当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。

一旦不再需要产生更多的值,生成器函数可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。

Note:

一个生成器不可以返回值: 这样做会产生一个编译错误。然而return空是一个有效的语法并且它将会终止生成器继续执行。

yield关键字

生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

Example #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> $i++<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>?&gt;
登录后复制

以上例程会输出:

<span style="color: #800080;">1</span><span style="color: #800080;">2</span><span style="color: #800080;">3</span>
登录后复制

Note:

在内部会为生成的值配对连续的整型索引,就像一个非关联的数组。

如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用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> = ;PHP;Likes dollar signs2<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> =&gt; <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> =&gt; <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>?&gt;
登录后复制

以上例程会输出:

<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> =&gt; <span style="color: #800080;">$value</span>);
登录后复制

生成null值

Yield可以在没有参数传入的情况下被调用来生成一个 NULL值并配对一个自动的键名。

Example #3 生成NULLs

<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>?&gt;
登录后复制

以上例程会输出:

array(<span style="color: #800080;">3</span><span style="color: #000000;">) {  [</span><span style="color: #800080;">0</span>]=&gt;<span style="color: #000000;">  NULL  [</span><span style="color: #800080;">1</span>]=&gt;<span style="color: #000000;">  NULL  [</span><span style="color: #800080;">2</span>]=&gt;<span style="color: #000000;">  NULL}</span>
登录后复制

使用引用来生成值

生成函数可以像使用值一样来使用引用生成。这个和returning references from functions(从函数返回一个引用)一样:通过在函数名前面加一个引用符号。

Example #4 使用引用来生成值

<span style="color: #000000;">php</span><span style="color: #0000ff;">function</span> &amp;<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> &gt; 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> &amp;<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>?&gt;
登录后复制

以上例程会输出:

<span style="color: #800080;">2</span>... <span style="color: #800080;">1</span>... <span style="color: #800080;">0</span>... 
登录后复制

Generator delegation via yield from ¶

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>?&gt;
登录后复制

以上例程会输出:

<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>-&gt;<span style="color: #000000;">getReturn();</span>?&gt;
登录后复制

以上例程会输出:

<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>
登录后复制

Comparing generators with Iterator objects

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>-&gt;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>-&gt;fileHandle, 0<span style="color: #000000;">);        </span><span style="color: #800080;">$this</span>-&gt;line = <span style="color: #008080;">fgets</span>(<span style="color: #800080;">$this</span>-&gt;<span style="color: #000000;">fileHandle);        </span><span style="color: #800080;">$this</span>-&gt;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>-&gt;<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>-&gt;<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>-&gt;<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>-&gt;<span style="color: #000000;">line) {            </span><span style="color: #800080;">$this</span>-&gt;line = <span style="color: #008080;">fgets</span>(<span style="color: #800080;">$this</span>-&gt;<span style="color: #000000;">fileHandle);            </span><span style="color: #800080;">$this</span>-&gt;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>-&gt;<span style="color: #000000;">fileHandle);    }}</span>?&gt;
登录后复制

This flexibility does come at a cost, however: generators are forward-only iterators, and cannot be rewound once iteration has started. This also means that the same generator can't be iterated over multiple times: the generator will need to either be rebuilt by calling the generator function again, or cloned via the clone keyword.

 

摘自:http://php.net/manual/zh/language.generators.php

 

智能AI问答
PHP中文网智能助手能迅速回答你的编程问题,提供实时的代码和解决方案,帮助你解决各种难题。不仅如此,它还能提供编程资源和学习指导,帮助你快速提升编程技能。无论你是初学者还是专业人士,AI智能助手都能成为你的可靠助手,助力你在编程领域取得更大的成就。
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
关于CSS思维导图的课件在哪? 课件
凡人来自于2024-04-16 10:10:18
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2024 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号