第六章 迭代器、生成器
1.什么是迭代器
# 引子:如何从列表、字典中取值的 # index 索引 key ---- 只有知道索引和key的时候才能取值 # for 循环 # 凡是可以使用for循环取值的都是可迭代的 # 可迭代协议: 内部含有__iter__方法的都是可迭代的 # 迭代器协议: 内部含有__iter__方法和__next__方法的都是迭代器 # 什么是可迭代的 # 什么是迭代器 迭代器 = iter(可迭代的),自带一个__next__方法 # 可迭代器最大的优势就是节省内存 # 当列表不断增大,随之占的内存越来越大,但当调用__iter__的时候,占用内存非常小 # 例 # py2 range 不管range多少 会生成一个列表 这个列表将用来存储所有的值 # py3 range 不管range多少 都不会实际的生成任何一个值 # 迭代器的优势 # 节省内存 # 快:取一个值就能进行接下来的计算,而不需要等到所有的值都计算出来才开始接下来的运算 # 迭代器的特性:惰性运算 # 列表、字典、元组、字符串、集合、range、文件句柄、enumerate from collections import Iterable,Iterator print(range(10000000)) # range(0, 10000000) print(isinstance(range(1000000),Iterable)) # True # 判断是都可迭代 print(isinstance(range(1000000),Iterator)) # False # 判断是不是一个迭代器
for i in dir([1,2,3]): if i == '__iter__': print(i) # __iter__ 所以列表是可迭代的 for i in dir([1,2,3].__iter__()): if i == '__next__': print(i) # __next__ 所以list.__iter__()是迭代器
2.next关键字
# next 打印当前值,然后准备下一个值,for循环的机制 lst_iter = [1,2,3].__iter__() print(lst_iter.__next__()) # 1 print(lst_iter.__next__()) # 2 print(lst_iter.__next__()) # 3 # 使用while实现for循环 l = [1,2,3] lst_iter = iter(l) # l.__iter__() while True: try: print(next(lst_iter)) # lst_iter.__next__() except StopIteration: break # 1 # 2 # 3
3.生成器
# 生成器 Generator # 自己写的迭代器,就是一个生成器 # 两种自己写生成器(迭代器)的机制: # 生成器函数 # 生成器表达式 # 普通函数 def cloth(num): ret = [] for i in range(num): ret.append('cloth%s'%i) return ret # 生成器函数 def cloth_g(num): for i in range(num): yield 'cloth%s'%i # 凡是带有yield的函数就是一个生成器函数 # yield功能: # 记录当前所在的位置,等待下一次next来触发函数的状态 g = cloth_g(1000) print(next(g)) # cloth0 print(next(g)) # cloth1 print(next(g)) # cloth2 # 说明: # 生成器函数的调用不会触发代码的执行, # 而是会返回一个生成器(迭代器) # 想要生成器函数执行,需要用next # 例 def func1(): print('****') yield 1 func1() # 无结果
4.文件监听
# 使用生成器监听文件输入的例子 import time def listen_file(): with open('userinfo') as f: while True: line = f.readline()#readline不知道停止 if line.strip(): yield line.strip() time.sleep(0.1) g = listen_file() for line in g: print(line) # sadfasdf # asdfasdf # asdfasdf # a # b
5.send关键字
# 生成器函数,可以使用send在函数外部向函数内部传递参数, # 可以在执行过程中添加send传值以支持后续的操作,但是不可以 # 在第一次就是用send,如果有需要可以给函数添加参数 # 想给生成器中传递值,有一个激活的过程,第一次必须要用next触发这个生成器 def func(): print(1111) ret1 = yield 1 print(2222,'ret1 :',ret1) ret2 = yield 2 print(3333,'ret2 :',ret2) yield 3 g = func() #1- 生成一个生成器 ret = next(g) print(ret) # 1111 # 1 g.send('alex') # 在执行next的过程中,传递一个参数,给生成器函数的内部 # 2222 ret1 : alex
6.计算移动平均值
# 计算移动平均值 # 月度的天平均收入 # 200 300 500 # 200 250 333 def average(): sum_money = 0 day = 0 avg = 0 while True: money = yield avg sum_money += money day += 1 avg = sum_money/day g = average() next(g) print(g.send(200)) # 200 print(g.send(300)) # 250.0 # 运行过程 def average(): #1- sum_money = 0 #4- day = 0 #5- avg = 0 #6- while True: #13- money = yield avg #7- # 9- money=200 sum_money += money #10- day += 1 #11- avg = sum_money/day #12- g = average() #2- next(g) #3- 触发生成器,预激活 print(g.send(200)) #8- print(g.send(300)) #8- print(g.send(200)) #8- # 详细描述 def average(): # 1- 定义一个函数,看到yield,知道这个函数是生成器函数 sum_money = 0 # 4- day = 0 # 5- avg = 0 # 6- while True: money = yield avg # 7- 执行 yield avg,将第一个avg返回给next,虽然不会打印,但是值为0 # 9- 赋值money = 200 # 13- 执行到yield停止,并将200赋值给avg sum_money += money # 10- sum_money = 200 day += 1 # 11- day = 1 avg = sum_money/day # 12- avg 200/1 = 200,然后进行下一次循环 g = average() # 2- 代码不会执行,直接返回一个生成器g next(g) # 3- 触发生成器的第一步 # 7-返回值为0,但是不打印 print(g.send(200)) # 8- 相当于next,触发yield --> moeny = 200 # print(g.send(300))
7.预激生成器
# 预激生成器,因为生成器函数需要预先执行一下,才能继续执行 # 所以添加装饰器,实现预激活功能 def init(func): def inner(*args, **kwargs): ret = func(*args, **kwargs) next(ret) # 预激活 return ret return inner @init def average(): sum_money = 0 day = 0 avg = 0 while True: money = yield avg sum_money += money day += 1 avg = sum_money/day g = average() print(g.send(200)) print(g.send(200)) print(g.send(300))
8.yield from
def generator_func(): yield from range(5) # py3简写方法 yield from 'hello' # py3简写方法 # for i in range(5): # yield i # for j in 'hello': # yield j g = generator_func()
9.如何从生成器中取值
# 如何从生成器中取值 # 第一种:next,随时都可以停止,最后一次会报错 print(next(g)) print(next(g)) # 第二种:for循环,从头到尾遍历一次,不遇到break、return不会停止 for i in g: print(i) # 0 # 1 # 2# ..... # l # o # 第三种:list、tuple 数据类型的强转,会把所有的数据都加载到内存里,非常的浪费内存 print(g) #<generator object generator_func at 0x000001774066DD00> print(list(g)) # [0, 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o']
10.生成器小结
# 生成器函数 是我们python程序员实现迭代器的一种手段 # 主要特征是 在函数中,含有yield # 调用一个生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器) # 只有从生成器中取值的时候,才不会执行函数内部的代码,且每获取一个数据才执行得到这个数据的代码 # 获取数据的方式包括 next send 循环 数据类型的强制转化 # yield返回值的简便方法,如果本身就是循环一个可迭代的,且要把可迭代数据中的每一个匀速都返回,可以用yield from # 使用send的时候,在生成器创造出来之后需要进行预激,这一步可以使用装饰器完成 # 生成器的特点: 节省内存 惰性运算 # 当有非常多数据需要处理的时候,就需要用到生成器 # 生成器用来解决:内存问题和程序功能之间的解耦 # 用途一: # 例:读20000行的文件,筛选出所有带电话号码的行的内容,并将这个行的内容按照我想要的格式组合打印出来 # 比较不好的方式 # for i in 20000: # line # 整理50行 # print() # 比较好的方式 # def func1(): # for i in 20000: # yield line # def func2(): # 内容格式化 # 用途二: # 网络文件传输,断点续传 # 用途三: # 函数的解耦:要将功能性的代码尽量的拆分,提高代码的可读性,提高代码的复用性 # 生成器表达式
11.列表推导式
# 列表推导式 new_lst = [] for i in range(10): new_lst.append(i**2) print(new_lst) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] print([i**2 for i in range(10)]) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # 利用列表推导式打印列表元素的正数 l = [1,2,3,-5,6,20,-7] print([abs(i) for i in l]) # [1, 2, 3, 5, 6, 20, 7] print([abs(i) for i in l if i > 0]) # [1, 2, 3, 6, 20] # 30以内所有能被3整除的数的平方 print([i**2 for i in range(1,31) if i%3==0]) # [9, 36, 81, 144, 225, 324, 441, 576, 729, 900] # 例三:找到嵌套列表中名字含有两个‘e’的所有名字 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] for i in names: for j in i: if j.count('e') == 2: print(j) # 用列表推导式的方法和for循环代码思路一致 print([j for i in names for j in i if j.count('e') == 2]) # ['Jefferson', 'Wesley', 'Steven', 'Jennifer'] # 比较明朗的写法 print([name for name_lst in names for name in name_lst if name.count('e')==2]) # ['Jefferson', 'Wesley', 'Steven', 'Jennifer'] # 列表推导式 排序的时候 l = [i**2 for i in range(1,31) if i%3==0] print(l) # [9, 36, 81, 144, 225, 324, 441, 576, 729, 900]
12.生成器表达式
# 生成器表达式 节省内存,庞大数据量的时候,使用生成器表达式 # 使用方法和列表推导式很类似 g = (i**2 for i in range(1,31) if i%3==0) print(g) # 迭代器、生成器 # <generator object <genexpr> at 0x000002F66613DDB0> # 面试题注意:::::::::::: # 面试题一: 一个生成器只能取一次值 # 面试题二 生成器在不找它要值的时候始终不执行 # 当它执行的时候,要以执行时候的所有变量值为准 # 面试题一: 一个生成器只能取一次值 def demo(): # 1- 定义生成器函数demo for i in range(4): yield i g=demo() # 2- 调用demo函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器) g1=(i for i in g) # 3- 本质是利用生成器表达式创建生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器) g2=(i for i in g1) # 4- 本质是利用生成器表达式创建生成器函数,不会执行这个函数中的代码,只会获得一个生成器(迭代器) print(list(g1)) # 5- 开始取值,向g1要值,g1是一个生成器,g1向g要值,g也是一个生成器,g向demo要值,demo会给g1 -- 0,1,2,3 print(list(g2)) # 6- g2是一个生成器,向g1要值,g1向g要值,g也是一个生成器,g向demo要值,此时demo中的值已经被取完了 # [0, 1, 2, 3] # [] # 面试题二 生成器在不找它要值的时候始终不执行 # 当它执行的时候,要以执行时候的所有变量值为准 def add(n,i): return n+i def test(): for i in range(4): yield i g=test() # 将for循环转化为非循环代码 # for n in [1,10]: # g=(add(n,i) for i in g) # 转化为 # n = 1 # g=(add(n,i) for i in g) # n = 10 # g=(add(n,i) for i in g) # 因为生成器表达式只是创建生成器函数,***不会执行这个函数中的代码*** # 只会获得一个生成器(迭代器)g,这个g就是n=10的时候括号里for循环中的g # 所以list(g)中的g为n=10的时候的值,下面是推导过程 n = 10 # g=(add(n,i) for i in g) # g=(add(n,i) for i in (add(n,i) for i in test())) # g=(add(n,i) for i in (add(n,i) for i in [0,1,2,3])) # g=(add(10,i) for i in (add(10,i) for i in [0,1,2,3])) # g=(add(10,i) for i in (10,11,12,13)) g=(20,21,22,23) print(list(g)) # [20, 21, 22, 23]
一鼓作气,再而衰,三而竭。