zoukankan      html  css  js  c++  java
  • yield和send的执行循序彻底搞清

    yield:

    对于yield方法和Generator的send同时使用时的执行顺序一直搞不清,今天看到这篇

    理解PHP中的Generator

    加上测试,终于搞清了。

    总结一下上文中的结论:

    • Generator提供了一种方便的实现简单的Iterator(迭代器)的方式,使用Generator实现Iterator不需要创建一个类来继承Iterator接口。
    • Generator实现了Iterator中的5个方法,还提供了三个新方法,其中__wakeup是一个魔术方法,用于序列化,Generator实现这个方法是为了防止序列化。
    • Generator 对象不能通过 new 实例化。
    • yield关键字只能在函数中使用(你可以尝试下在函数外使用,看看会发生什么),而且使用了yield关键字的函数都会返回一个Generator对象。
    • yield语句有点像return语句,代码执行到yield语句,generator函数的执行就会终止,并且会返回yield语句中的表达式的值给 Generator对象,这跟return语句一样,不同的是,这返回值只是作为遍历Generator对象的当前元素,而不能赋值给其他变量。
    • 当对Generator对象继续迭代,generator函数中的yield后面的代码会继续执行,直到generator函数中的yield语句全部执 行完毕,或者是碰到generator函数中的空return语句(返回null的return语句),在generator函数中使用带有非null返 回值的return语句会报编译错误。
    • 如果yield后面没有任何表达式(变量、常量都是表达式),那么它会返回NULL,这一点跟return语句一致。
    • yield也可以返回键值对的形式。
    • 在send之前, 当$gen迭代器被创建的时候一个rewind()方法已经被隐式调用,这样rewind的执行将会导致第一个yield被执行, 并且忽略了他的返回值.真正当我们调用yield的时候, 我们得到的是第二个yield的值! 导致第一个yield的值被忽略。(这条来自鸟哥博客的总结,可以看“多任务协作”部分的例子理解)

     

    几个经典的例子帮助理解!

    1.经典的例子热身

    function xrange($start, $end, $step = 1) {
        for ($i = $start; $i <= $end; $i += $step) {
            yield $i;
        }
    }
    foreach (xrange(1, 1000000) as$num) {
        echo$num, "
    ";
    }
    $range = xrange(1, 1000000);
    var_dump($range); // object(Generator)#1
    var_dump($range instanceof Iterator); // bool(true)

    2.鸟哥博客中的例子

    在PHP中使用协程实现多任务调度,(确定要搞清楚为什按照这种方式输出. 以便后续继续阅读.的例子)在上边连接文章里有详细的解释了。

    3.一个读取文件的例子,同时使用了send函数

    /* a.log的内容 */
    aaaaa
    bbbbb
    ccccc
    ddddd
    function lineGenerator($file)
    {
        $fp = fopen($file, 'rb');
        while ($line = fgets($fp)) {
            var_dump(yield $line);
        }
    }
    
    $lines = lineGenerator("a.log");
    foreach ($lines as $line) {
        $lines->send('test');
        echo $line;
    }
    /* 输出结果 */

    string(4) "test" aaaaa NULLstring(4) "test" ccccc NULL

    之前对于这个输出结果一直理解不了,现在总结一下怎么分析:

    1.外层循环的每一次都会调用一次内层函数中yield的一行,执行完一次yield便停止执行。

    2.对于var_dump(yield $line) 这样的写法,在分析时可以拆分为$var = (yield $line);var_dump($var);便于理解。

    3.send()函数在调用时如果yield函数一次也没被执行过,则会先执行一次yield(其实是在创建迭代器时已经隐式的执行了rewind方法),再进行赋值,再执行next(send有一个next的功能)。

    根据上边的方法分析一下上边的例子:

    1.首先,根据总结的方法2先对lineGenerator函数进行修改,因为a.log就4行,索性可以不用循环了。修改后如下:

    function lineGenerator($file)
    {
        $fp = fopen($file, 'rb');
        $var = (yield fgets($fp));
        var_dump($var);
        $var = (yield fgets($fp));
        var_dump($var);
        $var = (yield fgets($fp));
        var_dump($var);
        $var = (yield fgets($fp));
        var_dump($var);
    }

    2.foreach开始时,($lines as $line)肯定是调用了current()方法赋值$line,就是内存循环要执行一次yield,此时内层代码执行到第二行,$line被赋值aaaaa。

    3.接着,外层循环调用了$lines->send('test'),这时的test值会赋值给内层函数当前的yield(就是第一个yield),然后执行第一个var_dump(),打印出了test,然后执行第二个yield,并把yield的值赋给send函数的返回值(就是bbbbb),内层代码停止。

    4.接着,外层循环执行了echo $line;所以打印出aaaaa

    5.foreach 进入下次循环,就是需要从函数上次停下的位置执行到下一个yield执行完,函数先执行第二个var_dump(),此时当前的yield是NULL,所 以打印出NULL,接着执行第三个yield,获取到a.log的第三行赋值给$line,即ccccc,函数执行停止。

    6.然后循环执行$lines->send('test'),函数的第三个var_dump()就打印出test,执行第4个yield,把a.log的ddddd赋给send的返回值。函数执行停止。

    7.外层循环执行echo $line;打印出ccccc

    8.foreach进入下一次循环,函数又要从上次停止的位置执行到下一个yield结束,就是函数中最后一个var_dump()执行,打印出NULL,因为后边没有yield了,代码执行结束。

    4.一个日志写入的例子,可以说明调用send时没有调用过yield的情况,用总结的第三个方法解决。

    function logger($fileName)
    {
        $fileHandle = fopen($fileName, 'a');
        while (true) {
            echo "aaa
    ";
            fwrite($fileHandle, yield."
    ");
            echo "bbb
    ";
        }
    }
    
    $logger = logger('a.log');
    var_dump($logger->send('Foo'));
    var_dump($logger->send('Bar'));
    /* 输出结果*/
    aaa
    bbb
    aaa
    NULL
    bbb
    aaa
    NULL

    分析:

    1.外部第一行$logger = logger('a.log'),此时只是生成了一个Generator对象,yield并没有执行。

    2.外部第二行$logger->send('Foo'),由于函数内部没有yield,所以会先执行一次yield后再执行赋值,next()操作。执行一次yield,会打印出aaa,注意yield执行完但是fwrite并没有执行,(这个可以算是send函数的初始化操作,哈哈)

    接着才是像正常情况下的send执行一样,进行next()操作,先把Foo赋值给当前的yield,执行fwrite写入,然后打印bbb,再打印aaa,执行

    fwrite($fileHandle,yield . " ");注意fwrite不执行,由于yield后边是空的,所以send的返回值赋值为NULL,内部函数停止,外部的第一个var_dump()会打印一个NULL

    3.外部第三行执行$logger->send('Foo'),此时内部函数整执行到fwrite,send把Foo赋值给当前的yield,然后fwrite写入,然后打印bbb,再打印aaa,执行到fwrite($fileHandle,yield . " ")停止执行。fwrite不执行,yield的返回值NULL,所以外部第二个var_dump()打印NULL
     
    附:yield输出key,value:
    $lineParts = explode(' ', $line, 2);
    yield $lineParts[0] => $lineParts[1];
  • 相关阅读:
    IT程序猿”是怎样练成的? 之 提升内驱力的7大秘籍转
    wp7 退出程序的提示对话框
    生活不容易
    Decorator模式学习
    用序列化方法实现的Prototype的深拷贝
    Observer pettern
    Adapter模式学习
    bridge模式学习
    Composite模式学习
    Prototype原形设计模式
  • 原文地址:https://www.cnblogs.com/leezhxing/p/4958541.html
Copyright © 2011-2022 走看看