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, "\n";
}
$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\n";
fwrite($fileHandle, yield."\n");
echo "bbb\n";
}
} $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 . "\n");注意fwrite不执行,由于yield后边是空的,所以send的返回值赋值为NULL,内部函数停止,外部的第一个var_dump()会打印一个NULL。

3.外部第三行执行$logger->send('Foo'),此时内部函数整执行到fwrite,send把Foo赋值给当前的yield,然后fwrite写入,然后打印bbb,再打印aaa,执行到fwrite($fileHandle,yield . "\n")停止执行。fwrite不执行,yield的返回值NULL,所以外部第二个var_dump()打印NULL。
 
附:yield输出key,value:
$lineParts = explode(' ', $line, 2);
yield $lineParts[0] => $lineParts[1];

yield和send的执行循序彻底搞清的更多相关文章

  1. spring多个AOP执行先后顺序(面试问题:怎么控制多个aop的执行循序)

    转载:spring多个AOP执行先后顺序(面试问题:怎么控制多个aop的执行循序) 众所周知,spring声明式事务是基于AOP实现的,那么,如果我们在同一个方法自定义多个AOP,我们如何指定他们的执 ...

  2. 面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)

    前言 这是一个真实的面试题. 前几天一个朋友在群里分享了他刚刚面试候选者时问的问题:"线程池如何按照core.max.queue的执行循序去执行?". 我们都知道线程池中代码执行顺 ...

  3. join控制线程的执行循序 T1 -> T2 -> T3

    /** * 控制线程的执行循序 T1 -> T2 -> T3 * join实现 */ public static void join(){ Thread t1 = new Thread(( ...

  4. sql执行循序

    (8) select (9) distinct (11) top 1 (6) Table1.id,COUNT(Table1.name) as nameCount (1) from Table1 (3) ...

  5. ASP.NET执行循序

    首先第一次运行一个应用程序(WebSite或者WebApplication都是Web应用程序)第一次请求 -> 1,IIS -> 2,aspnet_isapi(非托管dll) -> ...

  6. for循环的执行循序

    先上一段代码,大家说出此方法的执行结果: public class Print{ static boolean out(char c){ System.out.println(c); return t ...

  7. for循环中的条件执行循序

    问题: public class Main { public static void main(String[] args) { int i,n,length = 0; for(i=1;length& ...

  8. 使用yield和send实现简单的协程函数

    使用yield和send实现协程 协程的本质是在一个线程里实现多个任务之间的来回切换,我们使用yield和send可以实现简单的协程 def pro(): print(1) n = yield &qu ...

  9. 深入理解Python中的yield和send

    send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互. 但是需要注意,在一个生成器对象没有执行next方法之前,由 ...

随机推荐

  1. JavaScript中知而不全的this

    都说 JavaScript 是一种很灵活的语言,这其实也可以说它是一个混乱的语言.它把 函数式编程和 面向对象编程糅合一起,再加上 动态语言特性,简直强大无比(其实是不能和C++比的,^_^ ). 这 ...

  2. monkeyrunner之测试结果判断(八)

    monkeyrunner的功能脚本编写完成之后,我们就需要对结果进行判断,判断结果是否为我们预期的结果值.下面我们主要讲述2种方式判断结果. 方式一.monkeyrunner截图对比 这是monkey ...

  3. Machine Learning Algorithms Study Notes(5)—Reinforcement Learning

    Reinforcement Learning 对于控制决策问题的解决思路:设计一个回报函数(reward function),如果learning agent(如上面的四足机器人.象棋AI程序)在决定 ...

  4. 【读书笔记《Bootstrap 实战》】2.作品展示站点

    假设我们已经想好了要给自己的作品弄一个在线站点.一如既往,时间紧迫.我们需要快一点,但作品展示效果又必须专业.当然,站点还得是响应式的,能够在各种设备上正常浏览,因为这是我们向目标客户推销时的卖点.这 ...

  5. java线程池(newSingleThreadExecutor())小应用

    创建单个线程,用来操作一个无界的队列任务,不会使用额外的线程.如果线程崩溃会重新创建一个,直到任务完成. 代码: import java.util.concurrent.ExecutorService ...

  6. 第10章 Java类的三大特性之一:多态

    1.Java中的多态 多态是指对象的多种形态,主要包括这两种: 1.1引用多态 a.父类的引用可以指向本类的对象b.父类的引用可以指向子类的对象举个例子:父类Anmail,子类Dog,可以使用父类An ...

  7. python_爬虫一之爬取糗事百科上的段子

    目标 抓取糗事百科上的段子 实现每按一次回车显示一个段子 输入想要看的页数,按 'Q' 或者 'q' 退出 实现思路 目标网址:糗事百科 使用requests抓取页面  requests官方教程 使用 ...

  8. python_面向对象编程

    一.编程范式 程序员通过特定的语法+数据结构+算法告诉计算机如果执行任务,实现这个过程有不同的编程方式,对这些不同的编程方式进行归纳总结得出来的编程方式类别,即为编程范式 编程范式:面向过程编程.面向 ...

  9. CRC校验码原理、实例、手动计算

    目录一.CRC16实现代码二.CRC32编码字符表三.CRC校验码的手动计算示例四.CRC校验原理五.CRC的生成多项式参考 一.CRC16实现代码 思路:取一个字符(8bit),逐位检查该字符,如果 ...

  10. 再谈Newtonsoft.Json高级用法

    上一篇Newtonsoft.Json高级用法发布以后收到挺多回复的,本篇将分享几点挺有用的知识点和最近项目中用到的一个新点进行说明,做为对上篇文章的补充. 阅读目录 动态改变属性序列化名称 枚举值序列 ...