迭代器
-
什么是迭代?
每次循环都是依赖上一个对象而来的 -
什么是迭代器协议?
一种约定,对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stopieteration的异常,以终止迭代,只能向前走,不能往后。eg:for循环是遵循迭代器协议去访问对象 -
什么是可迭代对象?
实现了迭代器协议的对象,通过内部定义一个_iter_方法,可迭代对象必须提供一个next方法
对于for循环,惯例会想,for循环因为遵循迭代器协议去访问对象,认为for循环的对象肯定都是可迭代对象了,但是for循环可以迭代遍历字符串、列表、元祖、字典、集合、文件对象,他们因为本身没有next方法,所以它们不是可迭代对象,但是在for循环中,调用了这些类型内部的方法_iter_,把它们变成了可迭代对象,从而调用可迭代对象的next方法去取值,而for循环会捕捉stopiteration异常,以终止迭代。
#迭代器
x='hello' #字符串本身不是可迭代对象
iter_test=x.__iter__() #字符串类型内部有_iter_函数,调用这个iter函数就让其变成了可迭代对象
print(iter_test) #iter_test就是一个可迭代对象了 #直接 iter()也可以的
#就会有_next_方法
print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
print(iter_test.__next__())
结果:
<str_iterator object at 0x0000022ACFB932E8>
h
e
l
l
o
- for循环的机制,就是遵循迭代器的协议,与索引机制没关系
#for循环的机制
l=[1,2,3,4]
for i in l: # 先i_1=l._iter_() 再 i_1._next_()
print i
#打印结束for循环可以捕捉到stopiteration异常,从而循环结束
- 从而可以学到一种新的取列表的值得方法
l=[1,2,3,4]
print(l[0]) #法一,索引法
l_iter=l.__iter__() #法二,先把列表l转化成一个迭代器
print(l_iter.__next__())
结果:
1
1
for循环的作用:
字符串、列表、元组这些都是序列,可以用索引法遍历,但是对于字典、集合、文件这些非序列类型,就必须要用迭代器了,遍历他们就得用for循环了。因此for循环提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的_iter_将其转化成一个迭代器,再使用迭代器协议去实现循环访问。所以能被for循环遍历的对象也得有个前提,那就是该类型
有_iter_方法
for循环打印字典,返回的是key值
dic={'a':123,'b':2}
dic_iter=dic.__iter__()
print(dic_iter.__next__())
结果: #之所以是key值是因为可迭代对象的next函数取key,与for 循环没啥关系
a
-
使用迭代器的好处
用的时候才给你 -
用while 去模拟for循环干的事
#用while 去模拟for循环
ll=[1,22,3,5,6,4]
ll_iter=ll.__iter__()
while True:
try:
print(ll_iter.__next__())
except StopIteration:
print('迭代完毕了,循环终止')
break
结果:
1
22
3
5
6
4
迭代完毕了,循环终止
lll=['yidai','erdai','sandai'] #全占
lll_iter=lll.__iter__() #就只得到了一个地址,所以在其他地方用的话,用它传输更省存储
print(lll_iter.__next__())
print(next(lll_iter)) #与上者效果相同,但next是内置函数,其原理就是在调用 lll_iter._next_()函数,只不过next是python提供的,而 _next_()是 由数据类型提供的
迭代器=可迭代对象
要想把一个对象变成迭代器,就调用_iter_方法
什么是可迭代对象:必须实现可迭代协议
什么是可迭代协议:对象必须有_next_方法
生成器
-
什么是生成器?
就是一种数据类型,这种类型自动实现了迭代器协议,不用再调用_iter_方法了,所以生成器就是可迭代对象。 -
在python中有两种表达方式
1、生成器函数
常规定义,但是用yied语句而不是return 返回结果,yied语句一次返回一个结果,在每个结果中挂起函数的状态,以使下次从它离开的地方继续执行。
def test():
yield 1
g=test()
print(g) #通过看打印结果,可知这就是个生成器
print(g.__next__()) #因为本身就是可迭代对象,所以就有_next_函数,打印出1
#print(g.__next__()) 因为作用相当于return 只有一个值,所以再打印就报错stopiteration
结果:
<generator object test at 0x0000022ACFB79A98>
1
yield可以多次,多次打印就不再出错了
def test():
yield 1
yield 1
yield 1
g=test()
print(g) #通过看打印结果,可知这就是个生成器
print(g.__next__()) #因为本身就是可迭代对象,所以就有_next_函数
print(g.__next__())
print(g.__next__())
结果:
1
1
1
理解生成器保留执行状态:
#附:生成器函数
def test2():
print('letty')
yield '我'
print('no')
yield '123'
#理解生成器(迭代器)的保留状态是什么意思
res=test2()
print(res.__next__())
第一次打印的结果:
letty
我
第二次打印的结果:从上次执行完的地方开始执行
no
123
生成器函数的好处:
可以再两次_next_中间增加代码
保留上次的执行状态
2、生成器表达式
ps:三元表达式
name='letty'
res='girl' if name=='letty' else 'unknow'
print(res)
结果:
girl
类似于列表推导,但是生成器返回按需产生结果的
ps:列表解析
#列表解析:
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']
如果进行列表解析:,达到相同的效果
直接生成一个列表,并且放到了内存
有个缺点,如果数量很大,会很占内存
l=['鸡蛋%s' %i for i in range(10)]
print(l)
结果:
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
#再加入三元表达式,没有四元表达式
ll=['鸡蛋%s' %i for i in range(10) if i>5]
print(ll)
结果:
['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
lao=('鸡蛋%s' %i for i in range(10)) #生成器表达式,其结果几乎不占内存
print(lao)
print(lao.__next__())
print(lao.__next__())
print(lao.__next__())
print(lao.__next__())
print(next(lao))
结果:
产生一个生成器:
<generator object <genexpr> at 0x00000220F8EEC990>
鸡蛋0
鸡蛋1
鸡蛋2
鸡蛋3
鸡蛋4
summary:
列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存,生成器要一个给一个
Python不但使用迭代器协议,让for循环更加通用,大部分内置函数,也是使用迭代器协议访问对象。eg sum、map、reduce、filter等等
[i for i in range(10)] #这会直接生成一个列表,如果数据非常大就非常占内存,可以[]变成()就会产生一个生成器,进行传输的时候就非常节省内存了。
特性总结:
1)语法上和函数类似:差别在于yield返回值
2)自动实现迭代器协议:
3)状态挂起
使用生成器的优点:
1)延迟计算,一次返回一个结果,不会一次生成所有结果,这对于大多数的数据处理会非常有用
2)有效提高了代码可读性
代码行数更少,在保证代码可读性的情况下,代码行数越少越好
3)