问题
- 怎么引发stopiteration异常? —-》在生成器中用return;或在生成器中不用循环
-
tmp
- https://mp.weixin.qq.com/s/GgamzHPyZuSg45LoJKsofA?
- 的学习笔记,用yield from重构代码中
- 事件循环+回调,
- OS将I/O状态的变化都封装成了事件,如可读事件、可写事件。并且提供了专门的系统模块让应用程序可以接收事件通知。python用selectors库
- 需事先知道代码执行到哪一块会阻塞
-
基础tips
- 线程是最小的执行单元,而进程由至少一个线程组成
- 一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。 - 多任务的实现有3种方式:
多进程模式;
多线程模式;
多进程+多线程模式。 - 真正的并行执行多任务只能在多核CPU上实现
- 真正地同时执行多线程需要多核CPU才可能实现。
- python由于有GIL,就算有多核cpu,也只能跑一个线程,所以python在多核情况下要用多进程+协程
- 在单核CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。
- 同步IO模型的代码是无法实现异步IO模型的。
异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程: - 用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。
线程
- 在做阻塞的系统调用时,例如sock.connect(),sock.recv()时,当前线程会释放GIL,让别的线程有执行机会。但是单个线程内,在阻塞调用上还是阻塞的
- Python中 time.sleep 是阻塞的,都知道使用它要谨慎,但在多线程编程中,time.sleep 并不会阻塞其他线程
- 所有的多线程还有通病。它们是被OS调度,调度策略是抢占式的,以保证同等优先级的线程都有均等的执行机会,那带来的问题是:并不知道下一时刻是哪个线程被运行,也不知道它正要执行的代码是什么。所以就可能存在竞态条件,
- 例如爬虫工作线程从任务队列拿待抓取URL的时候,如果多个爬虫线程同时来取,那这个任务到底该给谁?那就需要用到“锁”或“同步队列”来保证下载任务不会被重复执行
协程
用作协程的生成器的基本行为
- GEN_CREATED,GEN_RUNNING,GEN_SUSPENDED,GEN_CLOSED,详情见《流畅的python》
- 用
from inspect import getgeneratorstate getgeneratorstate(my_coro2)
来检查生成器的状态 - GEN_CLOSED,在调用close方法后,或遇到了stopiteration会出现这个状态
- GEN_SUSPENDED 表示在 yield 表达式处暂停
stopiteration
- 如果协程内没有处理异常,且遇到了异常时,协程会终止。如果试图重新激活协程,会抛出 StopIteration 异常。
- 如果不管协程如何结束都想做些清理工作,要把协程定义体中相关的代
码放入 try/finally 块中
生成器的throw方法
如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,包括GeneratorExit
如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中
def myGenerator(): try: yield 1 yield 2 except ZeroDivisionError: pass gen = myGenerator() print(next(gen)) # print(gen.close()) print(gen.throw(GeneratorExit))
这段代码会报错,原因是,没有处理GeneratorExit异常,但是,调用close方法不会报错,因为close方法就算是生成器没有处理GeneratorExit异常,也不会报错。
对于有except GeneratorExit 的情况:throw(GeneratorExit)后需要有yield,否则会报错StopIteration;但close方法调用后,生成器中不能有yield,否则会报RuntimeError。这一点,说明生成器的throw(GeneratorExit)和close不是完全等价的。
生成器的close方法
- 致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常,后续执行的语句中,不能再有yield语句,否则会产生RuntimeError,但是,若是直接通过throw一个GeneratorExit ,代码后面可以有yield
def myGenerator():
try:
yield 1
yield 2
except GeneratorExit:
print('sd')
# print('fin')
# yield 11
def tt(q):
yield from q
q=myGenerator()
gen = tt(q)
print(next(gen))
# gen.close()
gen.throw(GeneratorExit)
- 这个例子说明,close方法和throw(GeneratorExit)方法有不同,这里调用close不会出错,但调用throw(GeneratorExit)会出错
- 下面是《流畅的python》的原文
如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器
上调用 close() 方法,那么在子生成器上调用 close() 方法,如
果它有的话。如果调用 close() 方法导致异常抛出,那么异常会
向上冒泡,传给委派生成器;否则,委派生成器抛出
GeneratorExit 异常(就是说,在子生成器上调用玩close方法后,还会在委派生成器上throw一个GeneratorExit或调用close方法,具体用throw还是close,看最初传给委派生成器的是哪一个)。
GeneratorExit
- 当一个生成器对象被销毁时,会引发close方法,或者其他方式引起的GeneratorExit,但不是通过throw引起的,因为1,生成器内没有捕捉GeneratorExit的话会报错,2,生成器内,捕捉GeneratorExit的话,会去返回下一个yield,而通过实验,发现若有下一个yield时,会报RuntimeError
- close方法和throw引起GeneratorExit的不同:close方法,和throw都会引起GeneratorExit,但是调用close后,生成器的后面不能再由yield了;而通过throw出来的异常,并且还被捕获了,则会在生成器中找下一个yield
- GeneratorExit异常继承自BaseException类。BaseException类与Exception类不同。一般情况下,BaseException类是所有内建异常类的基类,而Exception类是所有用户定义的异常类的基类。
- 主程序结束,若没有给生成器用close方法,当脚本运行完成后,会调用close方法。而GeneratorExit异常产生的时机,是在生成器对象被销毁之前
- 生成器对象的close方法会在生成器对象方法的挂起处抛出一个GeneratorExit异常。GeneratorExit异常产生后,系统会继续把生成器对象方法后续的代码执行完毕
yield from
使用 yield from 句法(参见 16.7 节)调用协程时,会自动预激【这里的预激是指隐式预激,可以判流畅的python的16.8的介绍yield from的伪代码:】,因此与示例 16-5 中的 @coroutine 等装饰不兼容。
from functools import wraps def coroutine(func): @wraps(func) def primer(*args,**kwargs): gen = func(*args,**kwargs) next(gen) return gen return primer @coroutine def averager2(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count def grouper(c): yield from c g=averager2() g=grouper(g) next(g) print(g.send(1)) print(g.send(2))
- 这里,无论代码的最后有无next(g),都会报错,有next(g)的话,由于g已经预激了,会None送给term;没有next(g)的话,虽然averager2已经预激,但是被grouper嵌套后,yield from的实现里面,需要先next一下,不然会报can’t send non-None value to a just-started generator
Python 3.4 标准库里的 asyncio.coroutine 装饰器(第 18 章介绍)不会预激协程,因此能兼容 yield from 句法。【指用asyncio.coroutine 装饰器得到的协程,不能用yield from 调用?】
- 我们无法在控制台中使用交互的方式测试这种行为,因为在函数外部使用 yield from(以及 yield)会导致句法出错。
- yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象。
- xxx = yield from yyy; yyy是子生成器,xxx的值是yyy中 子生成器终止时传给StopIteration异常的第一个参数
- 把各个 value 传给 grouper(调用方send数值)。传入的值最终到达 averager 函数中term = yield 那一行;grouper 永远不知道传入的值是什么。
- 调用方 main;委派生成器 (有yield from的)grouper;子生成器 (有yield 的)averager。
- 委派生成器相当于管道,所以可以把任意数量个委派生成器连接在一起:一个委派生成器使用 yield from 调用一个子生成器,而那个子生成器本身也是委派生成器,使用 yield from 调用另一个子生成器
如果调用的方法抛出 StopIteration 异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器
别人的总结
- 迭代器(即可指子生成器)产生的值直接返还给调用者
- 任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行
yield from
后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。 - 除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器
- 如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
- 当迭代器结束并抛出异常时,
yield from
表达式的值是其StopIteration 异常中的第一个参数。 - 一个生成器中的
return expr
语句将会从生成器退出并抛出 StopIteration(expr)异常。
asyncio
- event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。
- coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
- task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
- future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。
普通的方式
async def xxx:
xxx
- async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
import asyncio
async def execute(x):
print('Number:', x)
coroutine = execute(1)
print('Coroutine:', coroutine)
print('After calling execute')
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('After calling loop')
创建task
- 方法一:
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
loop.run_until_complete(task)
- 方法二
task = asyncio.ensure_future(coroutine)
print('Task:', task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
绑定回调
为某个 task 绑定一个回调方法
# 参数是task对象,task.result()是task绑定函数return的结果
def callback(task):
print('Status:', task.result())
task.add_done_callback(callback)