10-Python之路---迭代器和生成器
前言
假如现在有一个列表L = [1,2,3,4,5],如果需要获取列表里的内容,有几种方法?
第一种:
L[0]
第二种:
for i in L:
print(i)
如果用索引取值,我们可以取到任意位置的值,前提是你得知道这个值在什么位置。
如果用for循环来取值,我们把每个值都取到,不需要关心每个值的位置,因为只能按照顺序来取值。
但是,有没有人想过为啥可以使用for循环取值?
Python中的for循环
想搞清楚for循环,还是得从代码的角度看:
for i in [1,2,3,4,5]:
print(i)
上面这段代码没啥问题,但是我们换一种情况,循环一组数字12345,
for i in 12345:
print(i)
# 结果:
Traceback (most recent call last):
File "D:/Mark/学员问题.py", line 4, in <module>
for i in 12345:
TypeError: 'int' object is not iterable
报错了!莫慌,有报错提示TypeError: 'int' object is not iterable
,说int类型不是iterable,老马百度翻译了一下
也就是说int类型不是可迭代的!
迭代
什么是迭代
"可迭代"这个概念,我们可以从报错上分析,之所以12345不可以for循环,是因为它不可迭代,反过来意思就是,如果"可迭代",那么就可以被for循环!
我们知道,字符串,列表,元组,字典,集合都可以被for循环,说明它们是可迭代的。
from collections import Iterable
l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}
print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))
print(isinstance(s, Iterable))
打印结果都是True,那么迭代指的就是可以将数据一个挨着一个的取出来。
可迭代协议
能被for循环的就是可迭代的,但是for怎么知道谁是可迭代的呢?
假设我们有一个数据类型,希望这个类型里的东西可以被for循环,那么就必须满足for循环的要求,而这个要求就是"协议"
可以满足被迭代的要求就叫做可迭代协议,可迭代协议的定义非常简单,就是内部实现了__ iter__方法。
print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print(dir({1,2}))
在打印结果中,不难发现,都有一个__ iter__方法。也就是说要想可迭代,那么内部必须有一个__iter__方法
那么,__ iter__方法有什么用呢?
print([1,2].__iter__())
# 结果:
<list_iterator object at 0x000001D5CF34C898>
又出现一个不懂的单词iterator,翻译一下
迭代器
又是一个新问题,什么是迭代器?
虽然现在还不清楚,但是我们已经有了一个迭代器,我们来研究下这个列表的迭代器和列表的区别.
# 迭代器的方法 - 列表的方法
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
# 结果:
{'__next__', '__length_hint__', '__setstate__'}
可以看到,迭代器中多了三个方法,那么这三个都分别有什么用处?
iter_list = [1,2,3,4,5,6].__iter__()
#获取迭代器中元素的长度
print(iter_list.__length_hint__())
#根据索引值指定从哪里开始迭代
print(iter_list.__setstate__(4))
#一个一个的取值
print(iter_list.__next__())
print(iter_list.__next__())
在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。
如果我们一直取next取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉我们,列表中已经没有有效的元素了。
L = [1,2,3,4]
L_iter = L.__iter__()
while True:
try:
item = L_iter.__next__()
print(item)
except StopIteration:
break
迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。
动手试试:看看range()是个啥?
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
Python中提供的生成器:
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)
特点:惰性运算,开发者自定义
好处:节省内存。
栗子:
假如我去饭店吃面,我上来就要挑战100碗,老板娘答应了,然后开始下面给我吃【手动狗头】,她做一碗,我吃一碗,也可以做3碗,我吃3碗,而不是说等100碗全部做好,在给我,等她做完,面坨了,我也不想吃了(要节约粮食!!!)
def produce():
"""下面"""
for i in range(2000000):
yield "做好第%s份面"%i
product_g = produce()
print(product_g.__next__()) #要一碗
print(product_g.__next__()) #再要一碗
print(product_g.__next__()) #再要一碗
num = 0
for i in product_g: #要五碗
print(i)
num +=1
if num == 5:
break
生成器表达式
l1=[i for i in range(10)] #列表解析
g1=(i for i in range(10)) #生成器表达式
print(g1)
print(next(g1)) #next本质就是调用__next__
print(g1.__next__())
print(next(g1))
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。
面试题
# 面试一
def demo():
for i in range(4):
yield i
g=demo()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
# 面试二
def add(n,i):
return n+i
def test():
for i in range(4):
yield i
g=test()
for n in [1,10]:
g=(add(n,i) for i in g)
print(list(g))