[Python/并发] Python异步编程的关键字辨析:async / await / yield
0 序
- 本篇缘起于这段代码:
# 启动命令: uvicorn main:app
async def app(scope, receive, send): #`async def` 声明一个异步函数(协程函数)。调用它返回一个协程对象,需用 `await` 或事件循环驱动才会执行。这是 【ASGI 应用】的标准签名:`scope`(请求元数据字典)、`receive`(接收消息的异步可调用)、`send`(发送消息的异步可调用)。
"""最简 ASGI 应用"""
assert scope['type'] == 'http' # `assert` 断言:若 `scope['type']` 不是 `'http'`,抛出 `AssertionError`。作用:【防御性校验】,确保该应用只处理 HTTP 请求(ASGI 还支持 websocket、lifespan 等类型)
await send({ # `await` 等待 `send` 这个【异步可调用】完成。
'type': 'http.response.start', # 发送 `http.response.start` 消息:状态码 200,响应头 `content-type: text/plain`。这对应 HTTP 响应的起始行 + 头部。
'status': 200,
'headers': [[b'content-type', b'text/plain']],
})
await send({ # 再次 `await send`,发送 `http.response.body` 消息,响应体为字节 `b'Hello, Uvicorn!'`。 两次 `send` 调用必须按顺序 `await`,保证先写头再写体。
'type': 'http.response.body',
'body': b'Hello, Uvicorn!',
})
1 Python 异步编程的演变过程(选读)
缘起:Python的GIL(全局解释锁)
- 由于众所周知的【Python解释器】的
GIL(全局解释锁)的原因,导致其【线程】无法发挥【多核的并行计算能力】(当然,后来有了multiprocessing,可以实现【多进程并行】),显得比较鸡肋。 - 既然在
GIL之下,同一时刻只能有一个线程在运行,那么对于【CPU密集的程序】来说,线程之间的切换开销就成了拖累,而以【I/O为瓶颈的程序】正是协程所擅长的:
- 多任务并发(非并行),每个任务在合适的时候挂起(发起I/O)和恢复(I/O结束)
- Python中的协程机制经历了很长的一段发展历程。其大概经历了3个阶段:
- 最初的生成器变形
yield/send- 引入
@asyncio.coroutine和yield from- 在最近的 Python3.5 版本中引入
async/await关键字
从yield说起
- 先看一段普通的计算斐波那契续列的代码:
def old_fib(n):
res = [0] * n
index = 0
a = 0
b = 1
while index < n:
res[index] = b
a, b = b, a + b
index += 1
return res
print('-'*10 + 'test old fib' + '-'*10)
for fib_res in old_fib(20):
print(fib_res)
- 如果我们仅仅是需要拿到斐波那契序列的第n位,或仅仅是希望依此产生斐波那契序列,那么上面这种传统方式就会比较耗费内存。 这时,
yield就派上用场了。
def fib(n):
index = 0
a = 0
b = 1
while index < n:
yield b
a, b = b, a + b
index += 1
print('-'*10 + 'test yield fib' + '-'*10)
for fib_res in fib(20):
print(fib_res)
当一个函数中包含
yield语句时,python会自动将其识别为一个【生成器】。这时,fib(20)并不会真正调用函数体,而是以函数体为基础生成了一个【生成器对象实例】。
yield在这里可以保留fib函数的【计算现场】,暂停fib的计算并将b返回。而将fib放入for…in循环中时,每次循环都会调用next(fib(20)),唤醒生成器,执行到下一个yield语句处,直到抛出StopIteration异常。
next(g)函数 ≈g.send(val)
此异常会被for循环捕获,导致跳出循环。
Send来了
-
从上面的程序中可以看到,目前只有数据从fib(20)中通过
yield流向外面的for循环;如果可以向fib(20)发送数据,那不是就可以在Python中实现【协程】了嘛。 -
于是,Python中的生成器有了
send函数,yield表达式也拥有了返回值。 -
我们用这个特性,模拟一个额慢速斐波那契数列的计算:
def stupid_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_cnt = yield b
print('let me think {0} secs'.format(sleep_cnt))
time.sleep(sleep_cnt)
a, b = b, a + b
index += 1
print('-'*10 + 'test yield send' + '-'*10)
N = 20
sfib = stupid_fib(N)
fib_res = next(sfib)
while True:
print(fib_res)
try:
fib_res = sfib.send(random.uniform(0, 0.5))
except StopIteration:
break
其中next(sfib)相当于sfib.send(None),可以使得sfib运行至第一个yield处返回。后续的sfib.send(random.uniform(0, 0.5))则将一个随机的秒数发送给sfib,作为当前中断的yield表达式的返回值。
这样,我们可以从“主”程序中控制协程计算斐波那契数列时的思考时间,【协程】可以返回给【“主”程序】计算结果,Perfect!
yield from是个什么鬼?
yield from用于重构生成器,简单的,可以这么使用:
def copy_fib(n):
print('I am copy from fib')
yield from fib(n)
print('Copy end')
print('-'*10 + 'test yield from' + '-'*10)
for fib_res in copy_fib(20):
print(fib_res)
这种使用方式很简单,但远远不是
yield from的全部。yield from的作用还体现可以像一个管道一样将send信息传递给内层协程,并且处理好了各种异常情况,因此,对于stupid_fib也可以这样包装和使用:
def copy_stupid_fib(n):
print('I am copy from stupid fib')
yield from stupid_fib(n)
print('Copy end')
print('-'*10 + 'test yield from and send' + '-'*10)
N = 20
csfib = copy_stupid_fib(N)
fib_res = next(csfib)
while True:
print(fib_res)
try:
fib_res = csfib.send(random.uniform(0, 0.5))
except StopIteration:
break
如果没有
yield from,这里的copy_yield_from将会特别复杂(因为要自己处理各种异常)。
asyncio.coroutine和yield from
- yield from在asyncio模块中得以发扬光大。先看示例代码:
def smart_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.2)
yield from asyncio.sleep(sleep_secs)
print('Smart one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
def stupid_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.4)
yield from asyncio.sleep(sleep_secs)
print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [
asyncio.async(smart_fib(10)),
asyncio.async(stupid_fib(10)),
]
loop.run_until_complete(asyncio.wait(tasks))
print('All fib finished.')
loop.close()
asyncio是一个基于事件循环的实现异步I/O的模块。通过
yield from,我们可以将协程asyncio.sleep的控制权交给事件循环,然后挂起当前协程;之后,由事件循环决定何时唤醒asyncio.sleep,接着向后执行代码。
- 这样说可能比较抽象,好在asyncio是一个由python实现的模块,那么我们来看看asyncio.sleep中都做了些什么:
def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds)."""
future = futures.Future(loop=loop)
h = future._loop.call_later(delay, future._set_result_unless_cancelled, result)
try:
return (yield from future)
finally:
h.cancel()
首先,sleep创建了一个Future对象,作为更内层的协程对象,通过yield from交给了事件循环;其次,它通过调用事件循环的call_later函数,注册了一个回调函数。
- 通过查看Future类的源码,可以看到,Future是一个实现了
__iter__对象的生成器:
class Future:
def __iter__(self):
if not self.done():
self._blocking = True
yield self
assert self.done(), "yield from wasn't used with future"
return self.result()
...
那么当我们的协程
yield from asyncio.sleep时,事件循环其实是与Future对象建立了练习。每次事件循环调用send(None)时,其实都会传递到Future对象的__iter__函数调用;而当Future尚未执行完毕的时候,就会yield self,也就意味着暂时挂起,等待下一次send(None)的唤醒。
当我们包装一个Future对象产生一个Task对象时,在Task对象初始化中,就会调用Future的send(None),并且为Future设置好回调函数。
class Task(futures.Future):
def _step(self, value=None, exc=None):
try:
if exc is not None:
result = coro.throw(exc)
elif value is not None:
result = coro.send(value)
else:
result = next(coro)
else:
if isinstance(result, futures.Future):
if result._blocking:
result._blocking = False
result.add_done_callback(self._wakeup)
def _wakeup(self, future):
try:
value = future.result()
except Exception as exc:
self._step(None, exc)
else:
self._step(value, None)
self = None
- 预设的时间过后,事件循环将调用
Future._set_result_unless_cancelled:
class Future:
def _set_result_unless_cancelled(self, result):
"""Helper setting the result only if the future was not cancelled."""
if self.cancelled():
return
self.set_result(result)
def set_result(self, result):
"""Mark the future done and set its result. If the future is already done when this method is called, raises InvalidStateError. """
if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._result = result
self._state = _FINISHED
self._schedule_callbacks()
async和await
- 弄清楚了asyncio.coroutine和yield from之后,在Python3.5中引入的async和await就不难理解了:可以将他们理解成asyncio.coroutine/yield from的完美替身。当然,从Python设计的角度来说,async/await让协程表面上独立于生成器而存在,将细节都隐藏于asyncio模块之下,语法更清晰明了。
async def smart_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.2)
await asyncio.sleep(sleep_secs)
print('Smart one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
async def stupid_fib(n):
index = 0
a = 0
b = 1
while index < n:
sleep_secs = random.uniform(0, 0.4)
await asyncio.sleep(sleep_secs)
print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
a, b = b, a + b
index += 1
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(smart_fib(10)),
asyncio.ensure_future(stupid_fib(10)),
]
loop.run_until_complete(asyncio.wait(tasks))
print('All fib finished.')
loop.close()
想要继续弄清楚
async/await和asyncio.coroutine/yield from的区别,可以看看这篇文章。
2 辨析:async / await / yield / assert (必读)
综合辨析(必读)
| 关键字 | 作用 |
|---|---|
| async | 异步。与 def 连用声明协程函数(async def),也可用于 async for(异步迭代)、async with(异步上下文管理器)。标记该函数 / 语句可被事件循环调度,执行中【可暂停让出控制权】。 |
| await | 异步等待(而非阻塞等待)。只能在 async def 内部使用。等待一个可等待对象(协程、Future、Task)完成。执行到 await 时,【当前协程】暂停并让出【事件循环】,等结果就绪后恢复执行,从而实现并发而非阻塞。 |
| assert | 调试期断言。assert 条件, 消息 —— 条件为假时抛 AssertionError。用于变量校验和调试,不应用于运行时业务校验(python -O 优化模式下 assert 会被整体移除)。 |
| yield | 用于生成器函数(普通 def 内)。执行到 yield 时函数暂停并返回一个值,下次 next() 或 send() 时从暂停处恢复。用于惰性生成序列、节省内存;yield from 可委托子生成器。注意: yield 是同步机制,与 async/await 的【异步并发】是两套体系(async def 内不能用 yield,但可以用 async yield 构成【异步生成器】)。 |
async
英译:异步(async),与同步(sync)相对 => 用来【定义协程函数】,它本身不做等待、不切换任务,只是给【函数】打上【标签】
- 英文:异步的、非同步的,与同步(sync)相对。
async是 Python 的语法标记,用来定义协程函数,它本身不做等待、不切换任务,只是给函数打上标签。
Python 里两层作用
1. async def:定义协程函数(与【普通函数】相对)
async def func():
pass
- 加上
async def,这个函数就不再是【普通函数】,是协程函数。 - 调用
func()不会执行函数内部代码,返回一个【协程对象】(coroutine object)。
res = func()
print(type(res)) # <class 'coroutine'>
类比:写了一张【任务欠条】,并没有真正干活,要交给【事件循环驱动】。
2. 函数内部允许写 await
- 只有在
async def的函数体内,才可以使用await,普通函数写 await 直接语法报错。
async def demo():
await asyncio.sleep(1) # ✅允许(异步等待1s)
def normal():
await asyncio.sleep(1) # ❌语法错误 SyntaxError
记住分工:
async:标记函数,开门允许使用 await;只做标记,不执行、不等待await:驱动、挂起、等待可等待对象完成,让出协程执行权
async ≠ 自动并发 (必读)
- 很多人误区:写了 async 就【自动并发】了,不是!
import asyncio
async def work(n):
await asyncio.sleep(n) # 异步等待n秒
print(f"sleep {n} done")
async def main():
work(2) # ⚠只生成了【协程对象】,丢弃,警告,没有运行
await work(2) # 顺序等待2秒
await work(1) # 再等待1秒,总耗时3秒,不是【并发】
asyncio.run(main())
- 想要【并发】,必须手动包装成 【Task】,交给【事件循环调度】:
async def main():
t1 = asyncio.create_task(work(2))
t2 = asyncio.create_task(work(1))
await t1
await t2
async with语法糖:异步版本的 with 语句,专门用于异步上下文管理器 (必读)
async with是异步版本的 with 语句,专门用于【异步上下文管理器】,作用和普通with类似:自动做资源的申请 / 释放,区别是里面的进入、退出操作是异步 IO。
async with只能写在async def异步函数内部。- 对象必须实现
__aenter__/__aexit__两个异步方法。- 进入和退出都会自动
await,不用自己手动写。- 常见场景:
aiohttp会话、异步数据库连接、异步锁asyncio.Lock。
- 普通 with
with open("a.txt") as f:
data = f.read()
# 离开本代码块,自动调用 `__exit__` 关闭文件(同步)
底层调用对象的
__enter__()、__exit__(),都是【同步】方法。
async with
import aiohttp
async def demo():
async with aiohttp.ClientSession() as session:
resp = await session.get("https://www.baidu.com")
# 离开代码块,自动异步关闭 session
底层调用异步魔术方法:
__aenter__():异步进入上下文,需要await__aexit__():异步退出上下文,需要await普通
with不能调用异步的进入 / 退出逻辑,所以:必须用async with。
- 自己写一个异步上下文管理器
import asyncio
class MyAsyncCtx:
async def __aenter__(self):
print("异步:打开资源")
await asyncio.sleep(0.1)
return "资源对象"
async def __aexit__(self, exc_type, exc, tb): # 如果有异常,exc_type, exc, tb 会被设置。exec_type 是异常类型,exc 是异常对象,tb 是异常跟踪信息
print(f"异步:释放资源: exc_type={exc_type}, exec={exec}, tb={tb}")
await asyncio.sleep(0.1)
async def main():
async with MyAsyncCtx() as obj:
print(f"正在使用:{obj}")
asyncio.run(main())
out:
异步:打开资源
正在使用:资源对象
异步:释放资源: exc_type=None, exec=<built-in function exec>, tb=None
async 语法清单
async def fn()→ 协程函数,调用得到协程对象await obj→ 在async函数内,等待可等待对象(【协程/Task/Future】)async with→ 异步上下文管理器(异步版本 with,异步资源释放)async for→ 异步迭代器,异步版本for循环
和 yield 做对比记忆
| 关键字 | 作用 | 产物 |
|---|---|---|
async def |
标记【协程函数】 | 【协程对象】,靠 await 驱动 |
yield |
【生成器函数】标记 | 【生成器对象】,靠 next() / send() 驱动 |
历史溯源:
Python3.4 用生成器yield from模拟协程;
Python3.5 新增async/await独立语法,专门用于IO异步,和【普通生成器】解耦。
极简记忆口诀
- **async打标签,await来驱动;
- 只async不await,任务不会动。**
await
英译:异步等待(async wait)
特性总结
await只能写在被async修饰的函数(协程函数)内部。
async def函数调用不会直接执行,返回一个协程对象,必须交给【事件循环】执行(asyncio.run()/create_task)。
-
await后面必须是可等待对象:协程、Task、Future,不能直接await time.sleep(2),会报错。 -
await xxx:挂起当前【协程】,【让出事件循环】,去干别的活;等xxx这个异步操作完成后,再回来继续执行后面代码。 -
⚠️ 不是【阻塞】整个线程,只是让出当前协程的执行权,线程不卡住,事件循环可以调度其他协程运行。
-
await只是在单线程内做并发,CPU 密集任务不会提速;CPU 密集要用multiprocessing多进程。
对比:普通阻塞式 vs. 异步等待式
time.sleep(2): 阻塞整个线程,啥都干不了。await asyncio.sleep(2): 异步挂起,【线程空闲】,【事件循环】可以跑其他任务。
- 补充对比:
await asyncio.sleep(delay)(正确示范) vsasyncio.sleep(delay)(反面示范)
asyncio.sleep(delay)语法本身合法,但单独写这行代码毫无意义,不会产生等待效果。
asyncio.sleep(n)返回一个协程对象(coroutine object),它只是一个 “任务说明书”,不会自动执行、不会休眠。
import asyncio
# 只是创建了一个【协程对象】,什么都没做
c = asyncio.sleep(2)
print(type(c)) # <class 'coroutine'>
就像:
def func(): pass,调用f()得到返回值;但不加 await,协程不会被调度执行。
await asyncio.sleep(2)
- 在
async def函数内部使用- 把【协程】交给【事件循环】调度
- 【当前协程】挂起、让出执行权,等待 2 秒;期间【事件循环】可以跑【其它协程】
- 2 秒之后恢复继续向下执行
- ✔真正实现【异步休眠】
- ❌若只写
asyncio.sleep(2)
- 创建了 sleep 协程对象,直接丢弃,完全不执行
- 不会等待 2 秒,立刻打印 done
- Python 会抛出警告:
RuntimeWarning: coroutine 'sleep' was never awaited
简单Demo
import asyncio
async def say_after(delay, msg):
await asyncio.sleep(delay) # 异步等待,让出CPU
print(msg)
async def main():
task1 = asyncio.create_task(say_after(2, "hello"))
task2 = asyncio.create_task(say_after(1, "world"))
await task1 # 等待task1完成,期间可以跑task2
await task2
asyncio.run(main())
正确使用 asyncio.sleep 的方式x3种
- 核心:
asyncio.sleep()只是构造可等待对象;
await才是触发事件循环去等待这个对象完成。
import asyncio
async def foo():
# 方式1:await,最常用,等待它完成
await asyncio.sleep(1)
# 方式2:包装成Task,后台并发执行
task = asyncio.create_task(asyncio.sleep(1))
await task
# 方式3:直接await一个已创建的协程对象
coro = asyncio.sleep(1)
await coro
yield
英译:产出、生成、让步、交出 => 产出数据,暂停函数/交出执行权/下次接着跑
yield:动词,产出、生成、让步、交出。
- Python 里取「产出数据,交出执行权」两层含义。
Python 中含义: 把【普通函数】变成了【生成器函数】
yield用来把【普通函数】变成【生成器函数】(generator)。
当函数里面出现
yield,【调用函数】不会【执行函数体】,而是返回一个生成器对象。
- 2大核心行为:
- 产出值:
yield val,向外吐出一个值。- 交出(暂停)执行权:函数暂停在 yield 处,保存当前函数现场(局部变量、执行位置);下次迭代再从暂停点继续往下跑。
- 和
return区别:
return:直接结束函数,销毁现场。yield:暂停,保留现场,可继续运行。
Demo1 基础用法
def gen():
yield 1
yield 2
yield 3
g = gen() # 不会执行函数体,得到生成器对象
print(next(g)) # 1,执行到第一个yield暂停
print(next(g)) # 2,从上次暂停继续
print(next(g)) # 3
# next(g) # 再调用抛出 `StopIteration`
- 也可以
for循环遍历:
底层:隐式调用了
next()
for item in gen(): # 迭代器协议:实现 `__iter__` + `__next__` => `next(g)` → 等价调用 `g.__next__()`:执行到下一个`yield`,返回产出值;无更多数据抛`StopIteration`。
print(item)
其对应的伪代码逻辑:
g = gen()
while True:
try:
item = next(g)
print(item)
except StopIteration: # for 循环,会捕获`StopIteration`异常,结束循环,不向外抛出。
break
Demo2:大文件读取典型场景(节省内存)
- 不用一次性把全部内容读到内存,逐行生成:
def read_large_file(path):
with open(path, "r", encoding="utf-8") as f:
for line in f:
yield line.strip()
# 逐行拿,内存只存一行
for line in read_large_file("big.txt"):
print(line)
yield 的进阶:表达式形式 x = yield
yield不仅可以分步【产出数据】,还可以接收外部 send() 发送进来的值,实现简单协程。
def coro():
print("start")
x = yield 100 # 产出100;同时可以接收 send 过来的值赋值给x
print(f"收到 x={x}")
yield 200
c = coro()
next(c) # 预激,运行到 yield 100,产出100
c.send(888) # 向生成器发送888,x=888,继续运行到 yield 200
""" out:
start
收到 x=888
"""
早期 Python 的
asyncio底层就是基于yield from实现协程;后来才出现语法糖async/await。
yield from语法糖
yield from sub= “交给 sub 来干活,我帮你把它所有值吐出去,通信也打通”。
yield x:产出 x 这一个值yield from x:产出 x 里面全部的值
yield from x:把 x 当作【子迭代器】,【全权委托】给【子迭代器】,自动迭代、产出它的所有值。
x 可以是生成器、可迭代对象。
等价简写,不用手动写循环 + yield:
def outer():
yield from [1,2,3]
# 等价于
def outer():
for v in [1,2,3]:
yield v
- 关键能力1:自动迭代子生成器
def sub():
yield 10
yield 20
def main():
yield from sub()
# `for` / `list()` / `tuple()` 等【消费可迭代对象】,全部遵循【迭代器协议】,底层都是不断调用`next()`,捕获`StopIteration`结束。
list(main()) # [10,20]
和 for 循环一样,list 函数也隐含了 next 函数的调用 (
list()内部会反复调用next())。其底层伪代码:
result = []
it = main()
while True:
try:
val = next(it)
result.append(val)
except StopIteration:
break
list(可迭代对象):拿到【迭代器】,循环next(),捕获StopIteration停止,把所有返回值装进列表。
- 关键能力2:透传 send ()、throw ()、返回值
普通 for+yield 只能传值;
yield from会把外部send()、异常、子生成器return的返回值双向隧道转发,这是它最重要的特性。历史:asyncio 早期就是用
yield from实现【协程】,后来被await替代。
yield (同步生成器: 产出数据、暂停函数/下次再接着跑) vs await(异步等待/异步协程/async协程:挂起协程,让出事件循环等待IO) 对比 (必读)
| 关键字 | 归属 | 本质 | 作用 |
|---|---|---|---|
yield |
生成器 | 同步生成器 | 暂停函数,产出数据;可做简单协程;用于迭代、惰性求值 |
await |
async协程 | 异步协程 | 挂起协程,让出事件循环等待IO,等待别的异步任务完成 |
-
next(迭代器):驱动迭代器向前执行,拿取下一个值; -
for 循环内部反复调用
next(),捕获StopIteration终止; -
生成器是迭代器的一种,
yield暂停执行,next()恢复执行。 -
历史关系:
Python3.4:
yield from实现异步IO;
Python3.5:新增语法糖async / await,专门做异步IO,和普通生成器解耦。
通俗总结
yield:函数执行到这里,吐出一个数据,原地暂停,保存状态,下次接着跑。- 适合【惰性生成序列】,处理【超大数据】,【不一次性加载全部到内存】。
- 它是同步机制,不是异步IO;虽然可以模拟协程,但不等同
await。
Python 的生成器及生成器函数
概念辨析:生成器 = 迭代器的一种特殊实现 (必读)
-
迭代器 (Iterator):是一套协议。只要【对象】实现
__iter__()+__next__(),就是【迭代器】。__next__():返回下一个值,耗尽抛StopIteration
-
生成器 (Generator):迭代器的一种特殊实现。
函数内写
yield,调用函数得到【生成器对象】;它自动实现了【迭代器协议】。
结论:所有【生成器】都是【迭代器】,但【迭代器】不一定是【生成器】。
【小结】
- 【迭代器】是抽象协议;【生成器】是【迭代器的子类】实现,由
yield产生。 - 迭代器的通用能力:被
next()、for、list()、tuple()等消费。 send/throw/close是生成器的独有扩展方法,【普通迭代器】没有。yield from内部会调用这些【生成器专属方法】,完成【双向通信】。
send(value) :生成器函数(恢复生成器执行,同时向生成器内部的 yield 表达式【传入/赋予/覆盖】一个值)
概念定义(必读)
send(value)是生成器方法,作用:恢复生成器执行,同时向生成器内部的yield 表达式传入一个值。
next(g)等价于g.send(None)
关键区分
def gen():
x = yield 100 # yield表达式:产出100;同时可以接收send进来的值赋值给x
print(f"收到x={x}")
yield 200
- 预激:必须先
next(g)/g.send(None),运行到第一个yield暂停。 - 再调用
g.send(888):- 将
888送入当前暂停的yield处,赋值给x - 恢复生成器往下执行,直到遇到下一个
yield,产出200并暂停
- 将
g = gen()
next(g) # 运行到 yield 100,产出100,暂停
g.send(888) # 打印 收到x=888;产出200,暂停
生成器函数对比: next() vs send()
next(g)=g.send(None):恢复执行,传入的值固定是Noneg.send(val):恢复执行,可以传入自定义值;首次不能send非None,会报错
只有
x = yield xxx这种yield表达式,才能接收send函数传入的值;
单纯yield xxx(语句形式),send传进去的值会被丢弃。
yield from 与 send
yield from会自动把外部的send()透传给子生成器,不用自己处理。
总结
next():只管唤醒生成器,送进去Nonesend(val):唤醒生成器,并且给暂停位置的yield表达式投递一个外部值,实现生成器双向通信。
早期基于生成器的协程,就是靠
send()做调度;后来被async/await取代。
Y 推荐文献
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号