Python之迭代器 生成器
迭代器
1. 可迭代协议和迭代器协议
迭代器:Iterator iterable :可迭代的
可迭代协议:只要对象中包含了__iter__方法,就是可迭代的
迭代器协议:迭代器中有__next__和__iter__方法
迭代器实现了能从中一个一个的取值
查看是否可迭代
from collections.abcimport Iterable # Iterable检测一个对象是否可迭代
print(isinstance(对象,数据类型/iterable)) #检测对象是否是该数据类型或者是否可迭代 print(dir(对象)) # 可以查看对象可以使用的功能 print(set(dir(对象))-set(dir(对象))) # 可以求这两个对象之间可使用的功能的差集
查看是否是迭代器
from collections.abc import Iterator # Iterator判断是否是迭代器 print(isinstance([1,2,3],Iterator))
2. 迭代器
列表本身是可迭代的 但不是迭代器
from collections.abc import Iterable from collections.abc import Iterator # 注:python3.7中collections 模块弃用 需从collections.abc模块进行导入 li = [1, 2, 3] print(isinstance(li, Iterator)) # False print(isinstance(li, Iterable)) # True
列表怎么变成迭代器?
from collections.abc import Iterator lst_iterator = [1,2,3].__iter__() # 调用__iter__()方法 print(isinstance(lst_iterator, Iterator)) # True
Iter() 与 __iter__() 用于产生 iterator(迭代器)
__iter__ 迭代器协议
凡是实现__iter__协议的对象,皆是迭代器对象。(next()也得实现,不然没法产生数据)
Iter()迭代器工厂函数
凡是有定义有__iter__()函数,或者支持序列访问协议,也就是定义有__getitem__()函数的对象 皆可以通过 iter()工厂函数 产生迭代器(iterable)对象
3. 迭代器的取值
lst_iterator = [1,2,3].__iter__() print(lst_iterator.__next__()) print(lst_iterator.__next__()) print(lst_iterator.__next__()) # 超出范围后报错 StopIteration
4. 可迭代对象 迭代器小结
a. Python语言中可以用for循环遍历的,都是可迭代对象
b. 可迭代对象包含迭代器
c. 迭代器的本质:能够对python中的数据类型进行统一的遍历,不需要关心每一个值是什么
d. 迭代器属于惰性运算,可以节省内存空间
5. 迭代器有两种:
天生的:文件句柄
后天的:可迭代对象.__iter__()
文件句柄是一个迭代器,range是一个可迭代对象
生成器
1. Gerator 本质就是迭代器,生成器是自己实现
生成器有两种:生成器函数和生成器表达式
2. 生成器函数
a. 生成器函数在执行的的时候返回一个生成器
b. 调用生成器函数时,第一次都是不不执行,而是获取生成器对象
c. 生成器只能向下取值,不能往回找
d. 生成器只有在取值的时候,才会执行
def generator_func(): print(123) yield "aaa" print(456) yield "bbb"
# 需要先获取生成器 g = generator_func() # g 就是生成器对象 这一步的执行并不会真的执行generator_func 而是由generator_func返回一个生成器对象 print(g) # <generator object generator_func at 0x0000019899601B88> # 使用__next__()方法取值 ret = g.__next__() # ret 是第一个yield返回的值 print(ret) ret1 = g.__next__() print(ret1) # 使用for.. in .. 取值 for i in g: print("i:",i) # 每遍历一次 返回一个yield的值
3. yield关键字
a. 带yield的就是生成器函数
b. yield不会终止函数
c. yield后面不加的时候就会是返回None
4. 从生成器取值:
a. __next__ 有几个yield就可以取几次
b. for循环取值 正常取 for item in generator:
c. 其他数据类型强制转换 list(generator) 返回一个列表,里面放着生成器的所有内容
# 列表取值
def generator_func(): print(123) yield "aaa" print(456) yield "bbb" g = generator_func() print(list(g)) # ['aaa', 'bbb']
def generator_func(): print(123) yield "aaa" print(456) yield "bbb" # __next__()取值 generator_func().__next__() # 直接调用不可以,相当于每次都调用一个新的生成器 print(generator_func().__next__) # aaa generator_func().__next__() # 直接调用不可以,相当于每次都调用一个新的生成器 print(generator_func().__next__) # aaa # for循环取值 for i in generator_func(): # for 的时候可以直接遍历,因为只调用了一个生成器 print("i:",i)
# for和list取值混用 def func(): yield 1 yield 2 g = func() for i in g: print(list(g)) # 这里只会打印一个[2],因为前面for循环的时候就已经拿到了一个1,现在去list(g)只能取到后面的2
三个取值方法小结:最好使用for循环取值,list强转取值法不推荐,因为无法知道生成器中的数据有多少,双下next取值方法不是很常用,并且用起来比较麻烦。
5. yield from
yield from 后边需要跟可迭代对象
def func(): yield from [1,2,3] print('=============') yield from "adfd" for i in func(): print(i) # 结果 会按顺序依次拿到可迭代对象中的元素 ''' 1 2 3 ============= a d f d '''
6. send关键字
a. send 是往里面传进去参数值,send不返回值的时候和next是一样的,在生成器执行伊始,只能先用next
b. send传递参数的时候,生成器中必须要有一个未被返回的yield
c. 函数里有几个yield就有几个next,并且第一个next不能改变,后面的可以改为send,send和text的总和就是yield的个数
def func(): print(123) value = yield 1 # next就是执行到yield 1,当执行send的时候,就从value = send传进来的值开始执行 print(value) print(456) yield "***"+value+"***" g = func() print(g.__next__()) # 推动func的运行,打印123,直到yield返回一个1,就暂停了 print(g.send("aaa")) # 接着执行send的时候,把"aaa"传进去,赋值给value后,接着向下执行,打印456,***aaa***
def func(): print(1) yield 2 # next的时候就实行到这里,当send88进来的时候,并没有变量去接受88,, print(3) value = yield 4 # 在这里,并没有send传值进来,所以value打印出来是空的 print(5) yield value g = func() print(g.__next__()) # 2 print(g.send(88)) # 4 print(g.__next__()) # None
7. 带装饰器的生成器
def wrapper(func): # 生成器预激装饰器 def inner(*args,**kwargs): g = func(*args,**kwargs) # g就是一个生成器 g.__next__() # 激活生成器 return g return inner @wrapper def average_func(): total = 0 count = 0 average = 0 while True: value = yield average total += value count += 1 average = total/count g = average_func() print(g.send(30))
8. 列表表达式,列表推导式
# /的结果是浮点数 # 两个//数字就不是带小数点的 new_l = [i*i for i in [1,3,5]] print(new_l) print([i//2 for i in range(0,7,2)])
9. 生成器表达式
a = ("egg%d"%i for i in range(10)) # 三种方法从生成器取值: # 1. __next() print(a.__next__()) # 2. for for i in a: print(i) # 3. list强转 print(list(a))
10. 字典推导式
# 将一个字典的key和value值对换 mcase = {"a":10,"b":20} new_dic={mcase[k]:k for k in mcase} print(new_dic)
11. 集合推导式 (可以去重)
new_l = {i*i for i in [1,3,5]} print(new_l)
12. 示例
a. Python代码和列表表达式比较
l = [{'name':'alex','age':80},{'name':'egon','age':40},{'name':'yuan','age':30},{'name':'nezha','age':18}] new_l = [] for d in l: new_l.append(d["name"]) print(new_l) l = [{'name':'alex','age':80},{'name':'egon','age':40},{'name':'yuan','age':30},{'name':'nezha','age':18}] print([i["name"] for i in l]) print([i["name"] for i in l if i["age"] > 18])
b. 生成器表达式
# 30以内能被3整除的数 print([i for i in range(30) if i%3==0]) # 30以内能被3整除的数的平方 print([i*i for i in range(30) if i%3==0])
c. 生成器取值方法 for list 混用
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)) # 把g1里的值取出来之后,g1就是空的了 print(list(g2)) # g2就是空的 # 结果 # [0, 1, 2, 3] # []
d. 生成器的坑
def add(n,i): return n + i def test(): for i in range(4): yield i g = test() for n in [1,10,5]: g = (add(n,i) for i in g) print(list(g)) # [15, 16, 17, 18]
解析:
为什么结果是 [15, 16, 17, 18]???
按正常逻辑来说,执行过程如下:
1. n=1 for i in g: i的值 分别是 0 1 2 3,执行完add(n, i), g=(1, 2, 3, 4)
2. n=10 for i in g: i的值 分别是 1, 2, 3, 4,执行完add(n, i), g=(11, 12, 13, 14)
3. n=5 for i in g: i的值 分别是 11, 12, 13, 14,执行完add(n, i), g=(16, 17, 18, 19)
需要注意的点是,for循环中的g是生成器表达式,如果换做列表表达式,那上边的过程就是对的。对于生成器需要注意的点就是,生成器只有在取值的时候才会执行。
所以我们在最后调用list方法取值时,生成器g还未执行,而此时for循环已经执行完毕,n在内存中最后的值是5。此时 g = (add(n,i) for i in g),这里边的 g 和我们现在找的g 是一样的,所以最终的生成器是 g = (add(n,i) for i in g = (add(n,i) for i in g = (add(n,i) for i in test()))) 。计算结果即为 [15, 16, 17, 18]
小结:
1. 最常用的是列表推导式, 在工作中,尽量把列表变成生成器表达式。
2. 尽量让推导式简化操作,增强代码的可读性,如果推导式过于复杂,应该转换成普通的python代码。
3. 所有的列表推导式都可以转换成生成器表达式,应该多使用生成器表达式,少使用列表表达式。
4. 在代码里,多层嵌套for循环是禁忌,会大幅度增加代码的复杂度。