【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求解物理学中双弹簧质能系统的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 物理的 ...
随机推荐
- The repository for high quality TypeScript type definitions
Best practices This is a guide to the best practices to follow when creating typing files. There are ...
- Linux-CentOS6.4-PXE-DHCP-FTP
DHCP服务器地址:192.168.1.100TFTPfFF服务器地址:192.168.1.100FTP服务器地址:192.168.1.100安装树目录:/var/ftp/pub引导程序目录:/var ...
- maven学习笔记(定制普通Java一个项目)
创建一个新项目: mvn archetype:generate -DgroupId=cn.net.comsys.ut4.simpleweather -DartifactId=simple-weathe ...
- php 添加redis扩展(二)
php代码操作redis 1.连接 <?php //连接本地的 Redis 服务 $redis = new Redis(); $redis->connect('127.0.0.1', 63 ...
- hyper-v 中 安装 Centos 7.0 设置网络 教程
安装环境是: 系统:win server 2012 r2 DataCenter hyper-v版本:6.3.9600.16384 centos版本:7.0 从网上下载的 centos 7.0 如果找 ...
- Java的外部类和内部类+静态变量和非静态变量的组合关系
看的李刚<疯狂java讲义>,里面讲内部类的地方感觉有点散而且不全,看完之后还是不十分清楚到底怎么用,于是自己写了个程序测试了一下.看如下代码,即可知道外部类和内部类+静态成员和非静态成员 ...
- c#处理3种json数据的实例
网络中数据传输经常是xml或者json,现在做的一个项目之前调其他系统接口都是返回的xml格式,刚刚遇到一个返回json格式数据的接口,通过例子由易到难总结一下处理过程,希望能帮到和我一样开始不会的朋 ...
- C++库大全(转)
基础类1. Dinkumware C++ Library 参考站点:http://www.dinkumware.com P.J. Plauger编写的高品质的标准库.P.J. Plauger博士是Dr ...
- ERP_Oracle Erp 11i 和 R12的区别概述(概念)
2014-06-26 Created By BaoXinjian
- OAF_EO系列4 - Create详解和实现(案例)
2014-06-02 Created By BaoXinjian