首頁  >  文章  >  後端開發  >  PHP yield 協程 生成器用法的了解

PHP yield 協程 生成器用法的了解

coldplay.xixi
coldplay.xixi轉載
2020-07-02 18:02:152811瀏覽

PHP yield 協程 生成器用法的了解

寫在前面

這篇文章,要跟大家探討的是PHP yield 在生成器用法,不帶foreach for, while 迴圈的那種。就討論 yield 將一個函數變成生成器的用法。

關於yield 特性,是在開發 PHP5 時被提上日程,PHP5.5 版本正式加入。

關於yield的使用,我看到大部分文章都停留在,使用yield如何在foreach#中穿出數據,今天想跟大家講講生成器所有文法。

相關學習推薦:PHP程式設計從入門到精通

官網講解

產生器允許你在foreach 程式碼區塊中寫程式碼來迭代一組資料而不需要在記憶體中創建一個陣列, 那會使你的記憶體達到上限,或者會佔據可觀的處理時間。相反,你可以寫一個生成器函數,就像一個普通的自定義函數一樣, 和普通函數只返回一次不同的是, 生成器可以根據需要yield 多次,以便生成需要迭代的值。

看了下官網對他講解:php.net 生成器語法  . 每個字都認識,但似乎還是體會到它講的內涵。官網我們主要看兩部分內容:

  1. yield 的語法。

  2. 程式碼範例。

先說語法, yield 的左邊是賦值語句,右邊可以是值(也可是表達式) 。而yield 會先執行右邊的表達式,並且把值$value送到生成器外面。當生成器收到值後,會執行yield左邊的語句,賦值給$data.

<?phpfunction func(){
    $data = (yield [$express]);}

語法就這樣,估計大家還是有些懵,那就看看官網下面代碼例子吧,我看裡面例子參差不齊。

注意yield 外麵包的這一層括號,如果是在php5.5,右邊$express的優先權是判斷,可能會比左側$data的賦值語句低的。所以在php5用yield,yield 右邊是可運行表達式,左側需要接受回傳並賦值,那麼這個括號是必要的。在php7不會有這個問題。

透過例子來了解它

不論是學人類語言,電腦語言,都是模仿開始

#對於一個用人類語言來描述,都不那麼明晰時,所以那就透過例子告訴你它能做什麼,不能做什麼。

相關程式碼,我放到gitee了,希望你能複製到你本地運行下,親自運行感受下,有助於了理解接下來的內容。

git clone gitee.com/xupaul/PHP-generator-yie...

可以產生Generator

先定義一個函數,在函數內寫個yield 關鍵字,並將這個函數呼叫賦值給一個變數。一個生成器就產生了。

程式碼 /php-yield-test/yieldFunctions.php 是生成器依照不同語法組合定義了多個生成器。

測試程式碼 /php-yield-test/whatIsGenerator.php,用來檢查哪些函式能構成生成器,哪些不能。運行結果如下

test result

  1. 函數內必須有 yield 關鍵字,函數可以是全劇函數,或是類別的方法。
  2. 即使 yield 肯定不會被執行,也會產生生成器。請參閱:yield_func4
  3. 光禿禿 的 yield 關鍵字就行(不向外送出,不處理外面的輸入)。請參閱: yield_func2
  4. 函數內使用生成器並不能讓自己也成為生成器,請參閱:yield_func5
  5. eval函數中直接執行yield 會報錯, 請參閱:yield_func11

是的,函數內有沒有foreach,while,for 語句都不是關鍵,關鍵是yield. 生成器的型別判斷用$gen instanceof Generator

生成器的函數

Generator 物件是從generators傳回的.

Generator 物件不能透過new 實例化.

  • Generator::current — 返回当前产生的值
  • Generator::key — 返回当前产生的键
  • Generator::next — 生成器继续执行
  • Generator::rewind — 重置迭代器
  • Generator::send — 向生成器中传入一个值
  • Generator::throw — 向生成器中抛入一个异常
  • Generator::valid — 检查迭代器是否被关闭
  • Generator::__wakeup — 序列化回调
  • Gengerator::getReturn - Get the return value of a generator

摘自 php.net generator

看着以上方法,是不想起了Iterator, 他们的确很像。同时注意,官网zh语言版本的文档没有索引方法getReturn,访问也是404。文档以en版为准,ch做参考。

以上就是生成器所有的方法,我们一个个来看。

测试方法代码 /php-yield-test/generatorMothod.php, 这里面对每个方法都有使用举例,运行结果如下。

run result 2

run result 3

好接下来对举例做个一一讲解。

Generator::current

  • 返回当前产生的值
<?phpfunction yield_func(){
    yield 12;
    return &#39;a&#39;;}$gen = yield_func();$re = $gen->current();echo &#39;current return : &#39; . $re;

输出:

current return : 12

看到 php-yield-test/generatorMothod.php  代码。

通过第一个代码事例,可得,对一个generator调用current方法,才算真正开始执行。执行到yield为止。如果不能命中yield,则执行到函数结束。

非generoator会立马执行并得到结果,而非一个生成器对象。

通过例子2,调用current一次,两次呢,第一次可以看到代码执行日志,第二次,只是把上一次的结果返回给我们而已,并不是让该生成器重新执行。

通过例子1,调用该函数还会获取到返回值,返回的内容就是 yield 表达式左边的内容。如果表达式无内容,则是NULL.

Generator::send

  • 向生成器yield点中传入一个值,并返回下一次current值。
<?phpfunction yield_func(){
    $data = yield 12;
    echo &#39;get yield data: &#39; . $data;
    return &#39;a&#39;;}$gen = yield_func();$re = $gen->current();$gen->send(32);

输出:

get yield data: 32

例子3,是一个current,send的常规调用。调用current代码运行yield等到用户send输入参数。接收到输入后,继续运行。current能够接收到yield弹出的值,send返回值为空。

例子4,直接调用send,相当于调用current,send。不过current的返回值,并不会通过send传给用户。

例子21中,可以看到直接调用send(1),会运行生成器,并向第一个yield处输入1,继续运行至下一个yield的返回值value。所以,$gen->send(2),和 $gen->current() 结果都是同一个值。

也就是说:跳过current,直接调用send,会丢失第一次yield的弹出值。

Generator::next

  • 跳过中断,并让生成器继续执行
<?phpfunction yield_func(){
    echo &#39;run to code line: &#39; . __LINE__ . PHP_EOL;
    yield;
    echo &#39;run to code line: &#39; . __LINE__ . PHP_EOL;
    return $result;}$gen = yield_func();$gen->current();echo &#39;current called&#39; . PHP_EOL;$gen->next();

输出:

run to code line: 4current called
run to code line: 6

例子5,这是一个较为常规的调用,调用current代码运行yield等到用户输入,这是调用next跳过,让代码继续运行。

例子6,直接调用next,相当于调用currentnext。而且通过最后打印$result, 我们发现怎么有点像在调用 $gen->send(NULL);

Generator::rewind

  • 重置迭代器
<?phpfunction yield_func(){
    echo &#39;run to code line: &#39; . __LINE__ . PHP_EOL;
    $result = yield 12;
    echo &#39;run to code line: &#39; . __LINE__ . PHP_EOL;}$gen = yield_func();echo &#39;call yield_func rewind &#39; . PHP_EOL;$gen->rewind();

输出:

call yield_func rewind 
run to code line: 4

例子7,8 中,发现调用该方法,会导致隐式调用current

例子9 中,发现在执行过一个yield代码段后,再次调用该方法,会导致报错(哪怕该 生成器已结束)。

Generator::throw

  • 向生成器中抛入一个异常
<?phpfunction yield_func(){
    try {
        $re = yield &#39;exception&#39;;
    } catch (Exception $e) {
        echo &#39;catched exception msg: &#39; .$e->getMessage();
    }}$gen = yield_func();$gen->throw(new \Exception(&#39;new yield  exception&#39;));

输出:

catched exception msg: new yield  exception

通过以上简单的例子可得,throw 就是让yield这行代码产生异常,让外面的try catch 捕获我们生成的那个异常。

例子11中,构造生成器,并调用current方法,运行到yield处,再调用throw,就能捕获到异常。

例子12中,当调用send方法,跳过函数内yield代码时,再调用throw传入异常,就没法捕获了。

Generator::valid

  • 检查迭代器是否被关闭
<?phpfunction yield_func(){
    yield 12;
    return &#39;a&#39;;}$gen = yield_func();$gen->send(1);$check = $gen->valid();echo &#39;the generator valid ? &#39; . intval($check);

输出:

the generator valid ? 0

例子12中,发现current被隐式调用。

例子13中,可得,当生成器运行到yield代码段时,用valid函数检查,都会返回true

所以,别问我是否已运行,问就是运行。该方法用来获取是否关闭状态,不是 是否运行状态!运行到底,运行到return就是 关闭状态。

Generator::key

  • 返回当前产生的键
<?phpfunction yield_func(){
    yield 1 => &#39;abc&#39;;}$gen = yield_func();echo &#39;value is :&#39; . $gen->current() . PHP_EOL;echo &#39;key is: &#39; . $gen->key() . PHP_EOL;

输出:

value is :abc
key is: 1

从以上例子中,可得yield可显示设置返回的key.

例子15 中,发现key的分发规律和PHP数组键值发放策略是差不多的,默认从0开始,未指定则是以上一个数字key+1作为当前的key.

例子16 中,我们又发现current被隐式调用。

Generator::__wakeup

  • Generator::__wakeup — 序列化回调
<?phpfunction yield_func(){
    yield 1 => &#39;abc&#39;;}$gen = yield_func();try {$ser = serialize($gen);} catch (\Exception $e) {
    print_r($e->getMessage());}

输出:

Serialization of &#39;Generator&#39; is not allowed

这是一个魔术方法,见 PHP 魔术方法,也就是说 生成器 不能被序列化成一个字符串。

例子17就不用说了,看下例子18,看样子序列化成功了。也就是说一个生成器做为一个方法可以被序列化,当函数变成生成器时,就不能被序列化了。

Generator::getReturn

<?phpfunction yield_func(){
    yield 1 => &#39;abc&#39;;
    return 32;}$gen = yield_func();$gen->send(0);echo &#39;call yield_func return, and get: &#39; . $gen->getReturn();

输出:

call yield_func return, and get: 32

该函数就是获取生成器最后的返回值。如果没有return语句,或者没有执行到return语句,调用该函数得到的就是NULL。

例子19 可得,getReturn 能够获取到生成器最后的返回值。

例子19、20 可得,当生成器没有执行到return语句,或者没有执行到最后时,调用getReturn是会导致报错。

综上所述

到这里,我们就发现rewind,next__wakeup 这两个函数感觉没啥叼用呢,为啥还存在呢,因为Generator继承Iterator,自然就有了rewind, next方法,PHP 虽然支持方法覆盖,但子类的访问修饰符 不能缩紧,所以Generator只能重写这两个方法。 __wakeup 继承自 stdClass

状态转换

看图:

PHP yield 生命周期图

画了两个状态转换图,上面的要细致,繁复一点。下面的精简版,便于快速理解。

总结

以上就是关于 PHP 生成器的基础内容,希望你看了后对它有更进一步认识。下一讲,我们手把手一起来做一个任务调度器,实战一下。

有问题欢迎提问,谢谢大家!

PHP yield 協程 生成器用法的了解

以上是PHP yield 協程 生成器用法的了解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除