先看一个栗子:
# -*- coding:UTF-8 -*- __autor__ = 'zhouli' __date__ = '2018/12/6 21:08' # 生成器函数,函数里只要有yield关键字 def gen_func(): yield 1 def func(): return 1 if __name__ == "__main__": gen = gen_func() re = func() pass
生成器函数这个对象是是什么时候产生的呢?是python编译字节码的时候就产生了,
既然是生成器对象,那么一定可以使用for循环进行遍历,并且yield可以多次
def gen_func(): yield 1 yield 2 yield 3 yield 4
yield的特性:惰性求值, 延迟求值提供了可能
斐波那契数列的经典举例:
def fib(index): if index <= 2: return 1 else: return fib(index-1) + fib(index-2) print(fib(10))
这样虽然可以做出来,但是没有具体的过程,那改进一下
def fib2(index): relist = [] n,a,b = 0,0,1 while n<index: relist.append(b) a, b = b, a+b n += 1 return relist
假如说现在index很大,上亿,那内存就有可能不够了。
def fib2(index): n,a,b = 0,0,1 while n<index: yield b a, b = b, a+b n += 1
改成这样,内部没有维护一个列表,自然而然就不会消耗内存的
当然这样可以直接进行for循环了
那生成器的原理是什么呢?适用于什么场景呢?如何区别于函数呢?
def foo(): bar() def bar(): global frame frame = inspect.currentframe() # python.exe会用一个叫做 PyEval_EvalFramEx(c函数)去执行foo函数, 首先会创建一个栈帧(stack frame)
python一切皆对象,栈帧对象, 字节码对象
当foo调用子函数 bar, 又会创建一个栈帧
所有的栈帧都是分配在堆内存上,这就决定了栈帧可以独立于调用者存在
利用生成器表达式读取大文件:
有人可能讲了,for line in f.open()
但是如果只有一行呢?
f.read(4096) # 先读4096个字符 f.read(4096) # 自动再次读取4096个字符
# 500G, 特殊 一行 def myreadlines(f, newline): buf = "" while True: while newline in buf: pos = buf.index(newline) yield buf[:pos] buf = buf[pos + len(newline):] chunk = f.read(4096) if not chunk: # 说明已经读到了文件结尾 yield buf break buf += chunk with open("input.txt") as f: for line in myreadlines(f, "{|}"): print(line)
使用单线程去切换任务
1,线程是由操作系统切换的,单线程切换一位置我们需要自己去调度任务
2,不在需要锁,并发性高,如果单线程内切换函数,性能将远高于线程切换,并发性更高
传统函数调用 过程 A->B->C
我们需要一个可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行
出现了协程 -> 有多个入口的函数, 可以暂停的函数, 可以暂停的函数(可以向暂停的地方传入值)
def gen_func(): #1. 可以产出值, 2. 可以接收值(调用方传递进来的值) html = yield "http://projectsedu.com" print(html) return "bobby" #1. throw, close #1. 生成器不只可以产出值,还可以接收值 if __name__ == "__main__": gen = gen_func() #在调用send发送非none值之前,我们必须启动一次生成器, 方式有两种1. gen.send(None), 2. next(gen) url = gen.send(None) #download url html = "天青色等烟雨" print(gen.send(html)) #send方法可以传递值进入生成器内部,同时还可以重启生成器执行到下一个yield位置 print(gen.send(html)) #1.启动生成器方式有两种, next(), send
我们需要的是传入一个网址之后返回的内容在原封不动的传回来
在调用send之后也会执行下一个yield的结果,并且开始的时候必须使用next()或者使用send()一个空值
调用gen.close()生成器已经结束了
gen = gen_func() print(next(gen)) gen.throw(Exception, "download error") print(next(gen)) gen.throw(Exception, "download error")
这三个操作就可以模拟协程,可以暂停,关闭,发送异常
yield from语法
在解释之前先了解一下
from itertools import chain
chain函数可以将多个可迭代对象进行一个for循环
my_list = [1,2,3] my_dict = { "web1":"http://projectsedu.com", "web2":"http://www.imooc.com", } for value in chain(my_list,my_dict,range(10)): print(value)
yield from 后面加的是一个可迭代的对象
yield from会自动将对象迭代出来
def g1(iterable): yield iterable def g2(iterable): yield from iterable for value in g1(range(10)): print(value) for value in g2(range(10)): print(value)
range(10) >>> 0 1 2 3 4 5 6 7 8 9
可以看出这两个的区别
def g1(gen): yield from gen def main(): g = g1() g.send(None) #1. main 调用方 g1(委托生成器) gen 子生成器 #1. yield from会在调用方与子生成器之间建立一个双向通道
具体用法:
final_result = {} def sales_sum(pro_name): total = 0 nums = [] while True: x = yield print(pro_name+"销量: ", x) if not x: break total += x nums.append(x) return total, nums def middle(key): while True: final_result[key] = yield from sales_sum(key) print(key+"销量统计完成!!.") def main(): data_sets = { "面膜": [1200, 1500, 3000], "手机": [28,55,98,108 ], "大衣": [280,560,778,70], } for key, data_set in data_sets.items(): print("start key:", key) m = middle(key) m.send(None) # 预激middle协程 for value in data_set: m.send(value) # 给协程传递每一组的值 m.send(None) print("final_result:", final_result) # if __name__ == '__main__': main()
""" 看完代码,我们总结一下关键点: 1. 子生成器生产的值,都是直接传给调用方的;调用方通过.send()发送的值都是直接传递给子生成器的;如果发送的是 None,会调用子生成器的__next__()方法,如果不是 None,会调用子生成器的.send()方法; 2. 子生成器退出的时候,最后的return EXPR,会触发一个StopIteration(EXPR)异常; 3. yield from表达式的值,是子生成器终止时,传递给StopIteration异常的第一个参数; 4. 如果调用的时候出现StopIteration异常,委托生成器会恢复运行,同时其他的异常会向上 "冒泡"; 5. 传入委托生成器的异常里,除了GeneratorExit之外,其他的所有异常全部传递给子生成器的.throw()方法;如果调用.throw()的时候出现了StopIteration异常,那么就恢复委托生成器的运行,其他的异常全部向上 "冒泡"; 6. 如果在委托生成器上调用.close()或传入GeneratorExit异常,会调用子生成器的.close()方法,没有的话就不调用。如果在调用.close()的时候抛出了异常,那么就向上 "冒泡",否则的话委托生成器会抛出GeneratorExit异常。 """