一,迭代器协议和for循环工作机制

(一),迭代器协议

1,迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stopiteration异常,以终止迭代(只能往后走,不能往前退)

2,可迭代对象:实现了迭代器协议的对象(如何实现,对象内嵌一个__iter__()方法)

3,协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象

(二),for循环工作机制

for循环的本质:循环所有对象,全都是使用迭代器协议

正本清源:(字符串,列表,元组,字典,集合,文件对象)这些都不是可迭代对象,只不过在for循环时,调用了他们内部的__iter__方法,把他们变成了可迭代对象。然后for循环调用可迭代对象的__next__方法去取值,而且for循环会捕捉Stopiteration异常,终止迭代。

例1,__next__()模拟for循环

l = [1,2,3]
for i in l: #for循环遵循迭代器协议,先调用l.__iter__()将其转换成一个迭代器,然后使用__next方法得到每一个值
print(i) #for循环模拟
x = 'hello'
iter_test = x.__iter__() print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
'''输出:
h
e
l
l
o
'''

例2,取列表的两种方法

l = [1,2,3]
#第一种:索引
print(l[0])
#输出:l #索引遍历
index = 0
while index < len(l):
print(l[index])
index += 1
'''输出:
1
2
3
''' #第二种:__next__()方法
iter_l = l.__iter__()
print(iter_l.__next__())
#输出:l

(三)for循环存在的作用

序列类型:字符串,列表,元组都有下标,可以使用索引访问,但是非序列类型:字典,集合,文件就不行。

for循环基于迭代器协议提供了一个统一的可以遍历所有对象的方法。

#集合只能使用for循环
s = {'a','b','c'}
for i in s:
print(i)
'''输出:
a
c
b
''' #for循环字典名时,取到的值是字典的key
dic = {'aa':1,'bb':2}
iter_d= dic.__iter__()
print(iter_d.__next__())
#输出:bb #使用for循环文件句柄f的好处是:迭代器只在用的时候返回一个值,而不像readlines把所有内容都加在到内存里
f = open('text.txt','r+')
iter_f = f.__iter__()
print(iter_f.__next__(),end='')
print(iter_f.__next__(),end='')
#输出:1111
#输出:2 #使用while语句模拟for循环
l=[1,2,3,4,5]
diedai_l=l.__iter__()
while True:
try:
print(diedai_l.__next__())
except StopIteration:
print('迭代完毕了,循环终止了')
break
'''输出:
1
2
3
4
5
迭代完毕了,循环终止了
''' #内置方法next函数,就是在调用iter_l.__next__()
l = ['die','erzi','sunzi','chongsunzi']
iter_l = l.__iter__()
print(next(iter_l))
#输出:die #迭代器就是可迭代对象,遵循迭代器协议就是可迭代对象
#怎样把一个数据结构变成可迭代对象,就是调用__iter__()方法
#如果数据结构没有__iter__()方法,就不是可迭代对象 

二,生成器初探

(一),定义:生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议,(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象。

(二),分类:Python有两种不同的方式提供生成器

1,生成器函数:常规函数定义,但是,使用yield语句而不是return返回结果,yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

def test():
yield 1
yield 2 g = test()
print(g)
print(g.__next__())
print(g.__next__())
'''输出:
<generator object test at 0x000000000073D6D0>
1
2
'''

yield可以保存函数的状态,下一次运行从上一次yield开始,而不是从函数的头开始

g_l = (i for i in range(10) if i > 5)
print(g_l.__next__())
print(g_l.__next__())
#输出:6
#输出:7 def test():
print('开始生孩子了')
yield '我'
print('开始生儿子了')
yield '儿子'
yield 3
yield 4 res = test()
print(res.__next__())
print(next(res))
'''输出:
开始生孩子了

开始生儿子了
儿子
'''

从这个特点再看生成器函数的好处

#没有生成器之前的写法,等到包子全部做完后,才能开始吃包子
#缺点1:占空间大
#缺点2:效率低 def product_baozi():
ret = []
for i in range(100):
ret.append('包子%s' %i)
return ret baozi_list = product_baozi()
print(baozi_list)
#输出:['包子0', '包子1', '包子2', '包子3', '包子4',....'包子99'] #使用生成器函数,不用一次把该准备的东西全部准备完,再进行下一步;而是完成一步后直接给需要的人,然后从中断的地方再准备下一步
def product_baozi():
for i in range(100):
print('正在生产包子')
yield '一屉包子%s' %i #i=1
print('开始卖包子') pro_g = product_baozi()
print('来了一人吃包子',pro_g.__next__()) print('来了一人吃包子',pro_g.__next__())
'''输出:
正在生产包子
来了一人吃包子 一屉包子0
开始卖包子
正在生产包子
来了一人吃包子 一屉包子1
'''

2,生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建整个结果列表。

#三元表达式
name = 'alex'
test3 = 'SB' if name == 'alex' else '帅哥'
print(test3)
#输出:SB #列表解析
egg_list = []
for i in range(10):
egg_list.append('鸡蛋%s' %i)
print(egg_list)
#输出:['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
#等效操作:
egg_list1 = ['鸡蛋%s' %i for i in range(10)]
print(egg_list1)
#输出:['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] l1=['鸡蛋%s' %i for i in range(10) if i > 5 ]
print(l1)
#输出:['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] # l1=['鸡蛋%s' %i for i in range(10) if i > 5 else i] #没有四元表达式 #列表解析的缺点是在数据比较大的时候占很大的内存,接下来看生成器表达式 #生成器
laomuji = ('鸡蛋%s' %i for i in range(10))#把列表解析的[]变成(),生成器表达式
print(laomuji)
print(laomuji.__next__())
print(next(laomuji))
'''输出:
<generator object <genexpr> at 0x000000000118D888>
鸡蛋0
鸡蛋1
''' #生成器表达式:(i for i in range(100000))
print(sum((i for i in range(100))))
#省略一层括号()得到
print(sum(i for i in range(100)))
#生成器每次生成一个数据给迭代器使用,避免列表解析占用大量内存导致机器卡死

总结:

1),把列表解析的[]换成()得到的就是生成器表达式

2),列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

3),Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如,sum函数是python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议。所以,我们可以直接这样计算一系列的和:sum()

(三)生成器总结

下面以生成器函数为例进行总结

1,语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值。

2,自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常。

3,状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数,保留足够的信息,以便之后从它离开的地方继续执行。

优点一:生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用

#列表解析
sum([i for i in range(10000000)]) #内存占用大,机器容易卡死 #生成器表达式
sum(i for i in range(10000000)) #几乎不占内存

优点二:生成器还能有效提高代码可读性

def xiadan():
ret = []
for i in range(100):
ret.append('鸡蛋%s' %i)
return ret #使用生成器
def xiadan():
for i in range(100):
yield '鸡蛋%s' %i

这里,至少有两个充分的理由说明,使用生成器比不使用生成器代码更加清晰:

1,使用生成器以后,代码行数更少。大家要记住,如果想把代码写的Pythonic,在保证代码可读性的前提下,代码行数越少越好

2,不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,此时index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。

注意事项:生成器只能遍历一次

#下面这种做法就是把鸡蛋全下完放在一个篮子里
def get_population():
with open('人口普查', 'r', encoding='utf-8') as f:
ret = []
for i in f:
ret.append(i)
return ret res = get_population()
print(res) #改进版使用生成器
def get_population():
with open('人口普查', 'r', encoding='utf-8') as f:
for i in f:
yield i g = get_population()
print(g)
#print(g.__next__()['population']) #错误使用方法,g.__next__()是一行文件的记录,取回来后是一个字符串
#print(eval(g.__next__())['population']) #使用eval把字符串转换成字典,输出10,结果正确 #求所有人口的和,就是实现了sum的功能
res_sum = 0
for p in g:
p_dic = eval(p)
print(p_dic['population'])
res_sum += p_dic['population'] print(res_sum) #不如直接使用sum
print(sum(eval(p)['population'] for p in g)) #然后求各个省占总人口的百分比,下面语句错误,没有输出,因为迭代器已经把g用完了
for p in g:
print(eval(p)['population']) #也就是说构建一个迭代器后,使用__next__()取值出来,只能遍历一次

(四),生成器应用:生产者与消费者模型

不使用生成器情况下,需要把包子全部生产完,才能给顾客吃

import time

def producer():
ret = []
for i in range(100):
time.sleep(0.1)
ret.append('包子%s' %i)
return ret def consumer(res):
for index,baozi in enumerate(res):
time.sleep(0.1)
print('第%s个人,吃了%s' %(index,baozi)) res = producer()
consumer(res)

插入一个知识点yield的另一个特性:send的使用

#yield 两个特性
#1,相当于return控制的是函数的返回值
#2,x = yield 的另外一个特性,接受send传过来的值,赋值给x def send_test():
print('开始了')
x = yield #调用send的时候,在上一次程序结束的位置传值给yield
print('第一次',x)
yield 2
print('第二次') t = send_test()
res = t.__next__()
print(res)
res =t.send(None)#传的值是None,也可以是其他值如字符串:‘测试’
print(res)
'''输出:
开始了
None
第一次 None
2
'''

协程:单线程内实现并发

def consumer(name):
print('我是[%s],我准备开始吃包子了' %name)
while True:
baozi = yield
#print('%s 上桌' %baozi)
time.sleep(3)
print('%s 很开心的把 %s 吃掉了' %(name,baozi))
print('#####################') def producer():
c1 = consumer('wupeiqi')
c2 = consumer('yuanhao')
c1.__next__()
c2.__next__()
for i in range(3):
print('开始做第 %s 个包子' %i)
time.sleep(3)
print('第 %s 个包子做好了' %i)
c1.send('包子%s' %i)
c2.send('包子%s' %i) producer() '''输出:
我是[wupeiqi],我准备开始吃包子了
我是[yuanhao],我准备开始吃包子了
开始做第 0 个包子
第 0 个包子做好了
wupeiqi 很开心的把 包子0 吃掉了
#####################
yuanhao 很开心的把 包子0 吃掉了
#####################
开始做第 1 个包子
第 1 个包子做好了
wupeiqi 很开心的把 包子1 吃掉了
#####################
yuanhao 很开心的把 包子1 吃掉了
#####################
开始做第 2 个包子
第 2 个包子做好了
wupeiqi 很开心的把 包子2 吃掉了
#####################
yuanhao 很开心的把 包子2 吃掉了
#####################
'''

最新文章

  1. CAD打印线条太粗、线条颜色设置
  2. 放下恩怨,曝小米中兴投关键性一票让华为顺利取得5G短码控制权
  3. barManager 挤压后“ 自动换行”和“自动隐藏”的实现方法
  4. Junit初级编码(一)第一个Junit测试程序
  5. js-高级技术
  6. Python进程、线程、协程
  7. 【Web】Eclipse + Maven + Struts搭建服务器
  8. Tomcat 解决The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit
  9. OpenJudge/Poj 1083 Moving Tables
  10. Least Common Multiple
  11. HDU 献给杭电五十周年校庆的礼物 1290 递推
  12. 捕android程序崩溃日志
  13. vs2010等宽字体设置
  14. ●杜教筛入门(BZOJ 3944 Sum)
  15. Eclipse导出包含第三方Jar的工程
  16. 阅读 video on-screen display v6.0笔记
  17. Spring注解之 Transactional
  18. linux目录结构介绍
  19. 哪些因素影响ABBYY FineReader 12的识别质量
  20. 2018.07.03 POJ 1279Art Gallery(半平面交)

热门文章

  1. pymsql简单的使用
  2. Ubuntu 16.04安装Docker-CE
  3. Scrapy实战篇(六)之爬取360图片数据和图片
  4. 转载 修改oracle用户密码永不过期
  5. Egret飞行模拟-开发记录03-LoadingUI界面
  6. SDWebImageRefreshCached
  7. C语言数据结构基础学习笔记——树
  8. HDU1671 Phone List
  9. C# xml 读xml、写xml、Xpath、Xml to Linq、xml添加节点 xml修改节点
  10. tomcat启动问题 严重: End event threw exception