1>迭代器协议
1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,
要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)
2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)
3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具
(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
2>for循环机制
for循环的本质:循环所有对象,全都是使用迭代器协议。for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了。
列表,字符串,元组,字典,集合,文件对象等本质上来说都不是可迭代对象,
在使用for循环的时候内部是先调用他们内部的_iter_方法,使他们变成了可迭代对象,然后在使用可迭代对象的_next_方法依次循环元素,当元素循环完时,会触发StopIteration异常,for循环会捕捉到这种异常,终止迭代。
3>访问方式
1.下标访问方式
l = ['lebron', 'ingram', 'lozon', 'kuzma']
print(l[1])
print(l[2])
2.遵循迭代器协议访问方式
l = ['lebron', 'ingram', 'lozon', 'kuzma']
l_iter = l.__iter__()
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__())
print(l_iter.__next__()) #超出边界报错:StopIteration
3.for循环访问方式
for循环l本质就是遵循迭代器协议的访问方式,先调用l_iter = l.__iter__()方法,或者直接l_iter = iter(l),然后依次执行l_iter.next(),直到
for循环捕捉到StopIteration终止循环
l = ['lebron', 'ingram', 'lozon', 'kuzma']
for i in l: #l_iter = l.__iter__()
print(i) #print(l_iter.__next__())
4.用while去模拟for循环做的事情
l = ['lebron', 'ingram', 'lozon', 'kuzma']
l_iter = l.__iter__()
while 1:
try:
print(l_iter.__next__())
except StopIteration:
print('迭代完毕,循环终止')
break
5>for循环的意义
序列类型字符串,列表,元组都有下标,用下标访问方式。但是非序列类型像字典,集合,文件对象,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了。
6>生成器初探
生成器:可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
3.生成器的优点:Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
生成器小结:
1.是可迭代对象
2.实现了延迟计算,省内存
3.生成器本质和其他的数据类型一样,都是实现了迭代器协议,只不过生成器附加了一个延迟计算省内存的好处,其余的可迭代对象没有这点好处。
7>生成器函数
import time
def all_star():
print('2019年全明星正赛西部首发')
print('前场')
time.sleep(2)
yield '勒布朗·詹姆斯'
time.sleep(5)
yield '凯文·杜兰特'
time.sleep(5)
yield '保罗·乔治'
time.sleep(2)
print('后场')
time.sleep(2)
yield '斯蒂芬·库里'
time.sleep(5)
yield '詹姆斯·哈登'
time.sleep(2)
yield '替补阵容名单将于5天后公布'
all_star_player = all_star()
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
print(all_star_player.__next__())
def lay_eggs(num):
egg_list = []
for egg in range(num):
egg_list.append('蛋%s' %egg)
return egg_list
yikuangdan=lay_eggs(10) #我们拿到的是蛋
print(yikuangdan)
def lay_eggs(num):
for egg in range(num):
res='蛋%s' %egg
yield res
print('下完一个蛋')
laomuji=lay_eggs(10)#我们拿到的是一只母鸡
print(laomuji)
print(laomuji.__next__())
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
#演示只能往后不能往前
#演示蛋下完了,母鸡就死了
8>生成器表达式和列表解析
1.三元表达式
name = 'lebronjames'
mvp = '牛逼' if name == 'lebronjames' else '垃圾'
print(mvp)
2.列表解析
生成所有元素并组成一个列表
l1_zgj = ['总冠军+%s' % i for i in range(10)] #列表解析
l2_zgj = ['总冠军+%s' % i for i in range(10) if i < 7] #包含三元表达式
print(l1_zgj)
print(l2_zgj)
3.生成器表达式
l_mvp = ('总冠军+%s' % i for i in range(10)) #生成器表达式
print(l_mvp.__next__())
print(next(l_mvp))
#将列表解析的中括号换成小括号就成了生成器表达式
4.列表解析与生成器表达式的区别
列表解析相当于一次性生成所有的元素,而生成器表达式一次只生成一个,之后函数为挂起状态,等待下一次的next方法,再生成下一个。
#湖人队由于詹姆斯的强势加盟很快走上了复兴之路,老板珍妮巴斯思来想去决定下几个鸡蛋来报答詹姆斯。
egg_list = ['鸡蛋%s' %i for i in range(10)] #列表解析
#詹姆斯瞅着珍妮巴斯下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下
laomuji = ('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))
5.总结
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器
协议,所以,我们可以直接这样计算一系列值的和:
print(sum(x ** 2 for x in range(4)))
而不用多此一举的先构造一个列表:
print(sum([x ** 2 for x in range(4)]))
9>生成器总结
综上已经对生成器有了一定的认识,下面我们以生成器函数为例进行总结
- 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值
- 自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且, 在没有值可以在没有返回的时候,生成器自动产生StopIteration异常
- 状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行
优点一:生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。
#列表解析
sum([i for i in range(100000000)])#内存占用大,机器容易卡死
#生成器表达式
sum(i for i in range(100000000))#几乎不占内存
优点二:生成器还能有效提高代码可读性
不使用迭代器
#求球星名字所在的位置
def find_mvpp(list1):
player = []
if list1:
for index, person in enumerate(list1, 1):
player.append(index)
return player
find = find_mvpp(('lebron james', 'james harden', 'pual gorge', 'zimuge'))
print(find)
-----------------------------------------------------------------------------------------
#求一段文字中,每个单词出现的位置
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result
print(index_words('hello alex da sb'))
使用迭代器
def find_mvp(list1):
if list1:
for index, preson in enumerate(list1, 1):
yield index
find = find_mvp(('lebron james', 'james harden', 'pual gorge', 'zimuge'))
print(find.__next__())
print(find.__next__())
print(find.__next__())
print(find.__next__())
-----------------------------------------------------------------------------------------
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index
g=index_words('hello alex da sb')
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
#print(g.__next__())#报错
这里,至少有两个充分的理由说明 ,使用生成器比不使用生成器代码更加清晰:
- 使用生成器以后,代码行数更少。大家要记住,如果想把代码写的Pythonic,在保证代码可读性的前提下,代码行数越少越好
- 不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。
这个例子充分说明了,合理使用生成器,能够有效提高代码可读性。只要大家完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。那么,就能够理解为什么使用生成器比不使用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。