一,迭代器协议和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 吃掉了
#####################
'''

【笔记】Python基础四:迭代器和生成器的更多相关文章

  1. python基础8 -----迭代器和生成器

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

  2. 【Python基础】迭代器、生成器

    迭代器和生成器 迭代器 一 .迭代的概念 #迭代器即迭代的工具,那什么是迭代呢? #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值 while True: #只是单 ...

  3. Python基础之迭代器和生成器

    阅读目录 楔子 python中的for循环 可迭代协议 迭代器协议 为什么要有for循环 初识生成器 生成器函数 列表推导式和生成器表达式 本章小结 生成器相关的面试题 返回顶部 楔子 假如我现在有一 ...

  4. 1.17 Python基础知识 - 迭代器和生成器初识

    可循环迭代的对象称为可迭代对象,迭代器和生成器函数是可迭代对象. 列表解析表达式:可以简单高效处理一个可迭代对象,并生成结果列表 示例代码: [ i ** 2 for i in range(10) ] ...

  5. Python高手之路【九】python基础之迭代器与生成器

    迭代器与生成器 1.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外 ...

  6. python 基础(五) 迭代器与生成器

    迭代器和生成器 迭代器 iterator (1) 迭代对象: 可以直接作用于for循环的 称为可迭代对象(iterable)可以通过 isinstance 判断是否属于可迭代对象 可以直接作用于for ...

  7. python基础之迭代器、生成器、装饰器

    一.列表生成式 a = [0,1,2,3,4,5,6,7,8,9] b = [] for i in a: b.append(i+1) print(b) a = b print(a) --------- ...

  8. Python基础之迭代器、生成器

    一.迭代器: 1.迭代:每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值.例如:循环获取容器中的元素. 2.可迭代对象(iterable): 1)定义:具有__ite ...

  9. python基础之迭代器与生成器

    一.什么是迭代器: 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束. 迭代器是一个可以记住遍历的位置的对象. 迭代器的 ...

  10. 7th,Python基础4——迭代器、生成器、装饰器、Json&pickle数据序列化、软件目录结构规范

    1.列表生成式,迭代器&生成器 要求把列表[0,1,2,3,4,5,6,7,8,9]里面的每个值都加1,如何实现? 匿名函数实现: a = map(lambda x:x+1, a) for i ...

随机推荐

  1. docker 快速部署ES集群 spark集群

    1) 拉下来 ES集群  spark集群 两套快速部署环境, 并只用docker跑起来,并保存到私库. 2)弄清楚怎么样打包 linux镜像(或者说制作). 3)试着改一下,让它们跑在集群里面. 4) ...

  2. win10 solidity开发环境搭建

    1. 软件安装 1) 安装nodejs 安装完成后将node.exe所在路径加入环境变量PATH中,以便在cmd命令行中直接使用node和npm命令 下面的操作在git bash下进行 2) 安装so ...

  3. C# 6.0:Auto-Property initializer

    在之前的开发中,属性只能在构造函数中进行初始化,如果它有定义一个后台字段的话,那这个字段就就可以在定义的地方初始化.C# 6.0 引进了一个Auto-Property initializer机制使属性 ...

  4. 基于fastadmin快速搭建后台管理

    FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架:开发文档 下面对环境搭建简要概述,希望后来者能少走弯路: 1. 百度搜索最新版wampserver, 安装并启动 ...

  5. Python【每日一问】14

    问:请介绍一下Python中的 import 机制 答: import 语句结合了两个操作:1.它先搜索指定名称的模块 2.将搜索结果绑定到当前作用域中的名称. 如果指定名称的模块未找到,则会引发 M ...

  6. Unity 代码组件获取和使用、Resources加载、OnGUI、Time、Mathf、PlayerPref

    1.     游戏物体组件获取.添加组件(重要) 作业分析: 子弹生成:坦克生成----->坦克控制类里生成子弹 子弹飞行:子弹自己飞,不能通过坦克控制类进行管理: 获取代码组件,设置子弹速度: ...

  7. 转HDMI

    HDMI协议解析 2017年06月18日 14:19:27 阅读数:2987 转载请标明出处floater的csdn blog,http://blog.csdn.net/flaoter 本文从软件工程 ...

  8. mysql 下的update select from的两种方式比较

    工作中遇到需要将一个表中的数据按照对应规则填入别的表中的情况 例如 表1 a a1    a2 11     90889 32     31241 12     52123 表2 b b1     b ...

  9. hex转mif文件 verilog

    用FPGA来跑ARM 核的时候,刚开始将Keil编译产生的hex文件拿来仿真和下到板子上的时候,发现程序运行不正确.细细观察仿真波形发现,在Altera的ROM IP中直接调用Keil产生的hex文件 ...

  10. Android Studio无法识别手机

    1.代理配置 1.1 无FQ的网络:需要配置代理: 1.2 公司网:不需要配置代理: 2.检查驱动安装情况: 2.1 检查设备管理器中的驱动是否正常安装: 成功后: 3.手机开启开发者模式