【Python】迭代器、生成器、yield单线程异步并发实现详解
转自http://blog.itpub.net/29018063/viewspace-2079767
大家在学习python开发时可能经常对迭代器、生成器、yield关键字用法有所疑惑,在这篇文章将从理论+程序调试验证的方式详细讲解这部分知识,话不多说,直接进入主题。
一、迭代器(Iterater):
首先介绍迭代器,迭代器是访问集合元素的一种方式,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。是不是觉得跟for循环很像?但是迭代器有几个特性需记住:
1、访问者不需要关心迭代器内部结构,只需不断执行next()方法获取下一个内容;
2、不能随机访问集合中的某个值,只能从头到尾顺序的读取;
3、访问到一半时不能回退,不能访问之前的值;
4、适合遍历很大的数据集合,节省内存,提升速度。
根据以上几个特性应该对迭代器有所了解了吧,我这里再举一个例子便于加深理解,大家都玩过linux,在linux中有两个命令,vi和cat,作用都是显示出文件的内容,而当一个文件很大(比如1G)时,使用vi命令打开文件则明显变慢,都有体会吧,会卡很久;而使用cat命令则没有这个问题,无论多大的文件,执行cat命令时都会马上从文件第一行数据开始打印;其实原因也很简单,使用vi时需要将整个文件加载到内存中再显示出来,而使用cat时则从文件第一行记录开始逐行的读取到内存中显示,而已读取过的内容则直接释放掉,这样每次读取到内存中只有一行记录响应速度当然快了。
其实这里的cat正是运用了迭代器的思想,迭代器每次顺序取集合中的一个值到内存,用完即作废,再取下一个值,对应特性1,对于很大的文件遍历则速度很快,对应特性4;则缺点也是明显的,迭代器不能取集合中间某个值,对应特性2;前一个值读取完即回收内存,所以无法重复读取,对应特性3;讲到这里大家应该已经能充分理解迭代器的原理了,下面进行代码演示:
1、创建一个迭代器:
a = iter(["wang","xuqian","xiaozhuzi"]) # 已创建一个迭代器对象,设置好需要迭代的值
2、遍历迭代器数据:
print(a.__next__())
print(a.__next__())
print(a.__next__()) # 有三个值,于是执行三次next()方法
3、结果:
wang
xuqian
xiaozhuzi
就是这么简单,由于迭代器的特性,我们只能顺序依次进行取值,不能像list那样可以取集合中的任意值,在这里三个值都取出后如果再执行a.__next__(),则会报错:“已停止迭代”
Traceback (most recent call last):
print(a.__next__())
StopIteration
二、生成器(Generator)和yield关键字:
生成器定义:当一个函数被调用时,返回一个迭代器,那么这个函数就叫做生成器,如果函数中包括yield语法,则这个函数就是一个生成器
yield:效果就是使函数中断,并保存中断的状态,中断后,代码可以继续往下执行,过一段时间还可以重新调用这个函数,并且可以从上次yield的下面的一句代码开始执行;yield可以返回值,也可以接收send来的参数。
生成器和yield的解释比较抽象难以理解,别急,下面通过调试代码的方式展示他们的用法,以及yield的魅力!~
def cash(account): # 先定义一个函数,接收一个参数
while account > 0: # 当参数>0时
account -= 100 #减去100
yield 100 #中断函数吗,返回100
print("又来取钱啦!") #打印
根据生成器的定义,此时cash函数即为一个生成器,作为一个生成器,当被调用后产生的生成器对象肯定是支持迭代器接口,也就是生成器返回一个迭代器,下面我们进行验证:
a = cash(500) # 调用生成器,生成迭代器
print(a.__next__()) # 运用迭代器的next()方法打印第一个迭代值
执行结果为:100
以上验证了调用生成器cash后的确生成了一个迭代器,关于迭代器应该没有什么疑问了,可以持续next()取值直到 account == 0;而大家现在一定有个疑问,这里面的yield关键字的作用是什么?为什么打印结果没有print("又来取钱啦!")?那么下面我跑一遍程序,通过结果来分析代码执行顺序:
def cash(account):
while account > 0:
account -= 100
yield 100
print("从我开始往下执行!") print("代码已经执行完啦")
a = cash(500)
print("第0次执行我")
print(a.__next__())
print("第一次执行我")
print(a.__next__())
运行结果为:
第0次执行我
100
第一次执行我
从我开始往下执行!
代码已经执行完啦!
100
从运行结果可以推断出,当程序进行到a = cash(500) 时,函数并没有进行调用而是继续往下走打印“第0次执行我”,为什么?其实正是因为在函数定义的时候,检测到函数中写了yield关键字,此时这个函数就变成了一个生成器,于是函数暂停运行;当执行到第一个print(a.__next__()) 时,才开始真正的调用函数了,函数执行到yield 100 时,根据yield的特性,函数中断返回100并打印,程序继续向下执行打印“第一次执行我”;当执行到第二个print(a.__next__()) 时,继续调用函数,根据yield的特性,会接着上一次中断的位置继续执行函数,于是打印“从我开始往下执行!”“代码已经执行完啦”,而根据函数中的while循环,会再次执行循环代码,直到 yield 100,函数再次中断返回100并打印。到此,程序执行完毕!
三、yield单线程异步并发实现
根据以上程序演示,大家应该对yield和生成器的特性理解更加深刻了吧,yield打破了常规的代码执行顺序,甚至能跳出循环,而且下一次调用函数(生成器)时还可以接着上次的代码向下执行,是不是很牛X?那么,yield的特性到底有什么用呢?
举个例子,我去银行取钱,加入取款额度较大,100万~,银行则需要审核、调配资金等手续,假如需要1天的时间,那么这一天我都要等在那里,这显然是不合理的吧。最好的办法是我继续做别的事情,等银行审批结束后再通知我去取钱。对于我们写的代码也是一样,由于我们是单线程串行执行代码,当调用一个函数时,如果这个函数迟迟不返回结果怎么办?按照之前的思路,那么程序只能卡死在那里一直等待,就和去取钱等银行审批一个道理;而yield的特性,打破了这一约束,使得我们在单线程编程的过程中,依然可以实现异步并发!下面再看一段代码:
import time
def consumer(name):
print("%s 准备吃烧烤啦!" %name)
while True:
shaokao = yield
print("烧烤%s被%s吃了!" %(shaokao,name))
def producter(name):
c = consumer("A")
c1 = consumer("B")
c.__next__()
c1.__next__()
print("开始生火烤串啦!")
for i in range(3):
time.sleep(1)
print("烤了两串羊肉")
c.send(i)
c1.send(i)
producter("jiaqi")
根据前面讲的知识,应该可以得出执行结果:
A 准备吃烧烤啦!
B 准备吃烧烤啦!
开始生火烤串啦!
烤了两串羊肉
烧烤0被A吃了!
烧烤0被B吃了!
烤了两串羊肉
烧烤1被A吃了!
烧烤1被B吃了!
烤了两串羊肉
烧烤2被A吃了!
烧烤2被B吃了!
这里就实现了异步并发控制,一个函数和生成器之间调用,通过yield实现;这里面还有个知识点,c.send(i),前面讲过yield不仅能返回值,而且还能接收值,在这里send()大家可以理解为与next()一样,都是触发调用生成器中的代码,但next()可以理解为传一个空值给yield,send()则可传一个实际的值给yield。以上代码中将i值传给了生成器consumer中的Yield
【Python】迭代器、生成器、yield单线程异步并发实现详解的更多相关文章
- Python迭代器生成器与生成式
Python迭代器生成器与生成式 什么是迭代 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果.每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次迭 ...
- php为什么需要异步编程?php异步编程的详解(附示例)
本篇文章给大家带来的内容是关于php为什么需要异步编程?php异步编程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 我对 php 异步的知识还比较混乱,写这篇是为了 ...
- 最强Java并发编程详解:知识点梳理,BAT面试题等
本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...
- Javascript 异步加载详解
Javascript 异步加载详解 本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy ...
- storm源码之理解Storm中Worker、Executor、Task关系 + 并发度详解
本文导读: 1 Worker.Executor.task详解 2 配置拓扑的并发度 3 拓扑示例 4 动态配置拓扑并发度 Worker.Executor.Task详解: Storm在集群上运行一个To ...
- Mysql加锁过程详解(5)-innodb 多版本并发控制原理详解
Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...
- 【转】Python的hasattr() getattr() setattr() 函数使用方法详解
Python的hasattr() getattr() setattr() 函数使用方法详解 hasattr(object, name)判断一个对象里面是否有name属性或者name方法,返回BOOL值 ...
- 【python库模块】Python subprocess模块功能与常见用法实例详解
前言 这篇文章主要介绍了Python subprocess模块功能与常见用法,结合实例形式详细分析了subprocess模块功能.常用函数相关使用技巧. 参考 1. Python subprocess ...
- 利用python求解物理学中的双弹簧质能系统详解
利用python求解物理学中的双弹簧质能系统详解 本文主要给大家介绍了关于利用python求解物理学中双弹簧质能系统的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 物理的 ...
随机推荐
- unity中的欧拉角
unity中欧拉角用的是heading - pitch -bank系统(zxy惯性空间旋转系统):当认为旋转顺序是zxy时,是相对于惯性坐标系旋转.当认为旋转顺序是yxz时,是相对于物体坐标系旋转. ...
- Problem A+B(Big Integer)
/*======================================================================== Problem A+B(Big Integer) ...
- 【转】C#取硬盘、CPU、主板、网卡的序号 ManagementObjectSearcher
private void button1_Click(object sender, EventArgs e) { textBox1.Text = ""; foreach (stri ...
- 使用注解来构造IoC容器
用注解来向Spring容器注册Bean.需要在applicationContext.xml中注册<context:component-scan base-package=”pagkage1[,p ...
- 邮件发送工具类 SendMail.java
package com.util; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.Simp ...
- Linux 环境下开机自启动Oracle服务
使用如下命令查看Oracle相关服务是否已启动: ps aux | grep ora_ #若无ora_**_**相关的进程,则oracle数据库实例未启动 netstat -tlnup | grep ...
- (转帖)BootStrap入门教程 (一)
2011年,twitter的“一小撮”工程师为了提高他们内部的分析和管理能力,用业余时间为他们的产品构建了一套易用.优雅.灵活.可扩展的前端工具集--BootStrap.Bootstrap由MAR ...
- js/jquery 操作document对象
一.获取对象 //js获取的是dom对象,jquery获取的是jquery对象 //jquery对象可以输出dom对象,索引方式输出dom对象,eq()[]方式输出dom对象;eq()输出jquery ...
- java hashtable
java hashtable Hashtables提供了一个很有用的方法可以使应用程序的性能达到最佳. Hashtables(哈希表)在计算机领域中已不 是一个新概念了.它们是用来加快计算机的处理速度 ...
- 【jmter】逻辑控制器(Logic Controller)
1. Jmeter官网对逻辑控制器的解释是:“Logic Controllers determine the order in which Samplers are processed.”.意思是说, ...