Generator(生成器),入门初基,Coroutine(原生协程),登峰造极,Python3.10并发异步编程async底层实现

Generator(生成器),入门初基,Coroutine(原生协程),登峰造极,Python3.10并发异步编程async底层实现

    普遍意义上讲,生成器是一种特殊的迭代器,它可以在执行过程中暂停并在恢复执行时保留它的状态。而协程,则可以让一个函数在执行过程中暂停并在恢复执行时保留它的状态,在Python3.10中,原生协程的实现手段,就是生成器,或者说的更具体一些:协程就是一种特殊的生成器,而生成器,就是协程的入门心法。

    协程底层实现

    我们知道,Python3.10中可以使用async和await关键字来实现原生协程函数的定义和调度,但其实,我们也可以利用生成器达到协程的效果,生成器函数和普通函数的区别在于,生成器函数使用 yield 语句来暂停执行并返回结果。例如,下面是一个使用生成器函数实现的简单协程:

def my_coroutine():
while True:
x = yield
print(x)

# 使用生成器函数创建协程
coroutine = my_coroutine()

# 启动协程
next(coroutine)

# 在协程中传入数据
coroutine.send(1)
coroutine.send(2)
coroutine.send(3)

    程序返回:

➜  mydemo git:(master) ✗ /opt/homebrew/bin/python3.10 "/Users/liuyue/wodfan/work/mydemo/src/test.py"
1
2
3

    在上面的代码中,生成器函数 my_coroutine 使用了一个无限循环来实现协程的逻辑。每当调用 send 方法时,协程就会从 yield 语句处恢复执行,并将传入的参数赋值给变量 x。

    如此,就完成了协程执行-》阻塞-》切换-》回调的工作流模式。

    当然,作为事件循环机制,协程服务启动可能无限期地运行,要关闭协程服务,可以使用生成器的close()方法。当一个协程被关闭时,它会生成GeneratorExit异常,该异常可以用生成器的方式进行捕获:

def my_coroutine():
try :
while True:
x = yield
print(x)
except GeneratorExit:
print("协程关闭")

# 使用生成器函数创建协程
coroutine = my_coroutine()

# 启动协程
next(coroutine)

# 在协程中传入数据
coroutine.send(1)
coroutine.send(2)
coroutine.send(3)

coroutine.close()

    程序返回:

➜  mydemo git:(master) ✗ /opt/homebrew/bin/python3.10 "/Users/liuyue/wodfan/work/mydemo/src/test.py"
1
2
3
协程关闭

    业务场景

    在实际业务场景中,我们也可以使用生成器来模拟协程流程,主要体现在数据的IO流操作中,假设我们需要从本地往服务器传输数据,首先建立链接对象:

class Connection:


def __init__(self, addr):
self.addr = addr

def transmit(self, data):
print(f"X: {data[0]}, Y: {data[1]} sent to {self.addr}")

    随后建立生成器函数:

def send_to_server(conn):
while True:
try:
raw_data = yield
raw_data = raw_data.split(' ')
coords = (float(raw_data[0]), float(raw_data[1]))
conn.transmit(coords)
except ConnectionError:
print("链接丢失,进行回调")
conn = Connection("重新连接v3u.cn")

    利用生成器调用链接类的transmit方法进行数据的模拟传输,如果链接断开,则会触发回调重新连接,执行逻辑:

if __name__ == '__main__':


conn = Connection("v3u.cn")

sender = send_to_server(conn)
sender.send(None)

for i in range(1, 6):
sender.send(f"{100/i} {200/i}")

# 模拟链接断开
conn.addr = None


sender.throw(ConnectionError)

for i in range(1, 6):
sender.send(f"{100/i} {200/i}")

    程序返回:

X: 100.0, Y: 200.0 sent to v3u.cn
X: 50.0, Y: 100.0 sent to v3u.cn
X: 33.333333333333336, Y: 66.66666666666667 sent to v3u.cn
X: 25.0, Y: 50.0 sent to v3u.cn
X: 20.0, Y: 40.0 sent to v3u.cn
链接丢失,进行回调
X: 100.0, Y: 200.0 sent to 重新连接v3u.cn
X: 50.0, Y: 100.0 sent to 重新连接v3u.cn
X: 33.333333333333336, Y: 66.66666666666667 sent to 重新连接v3u.cn
X: 25.0, Y: 50.0 sent to 重新连接v3u.cn
X: 20.0, Y: 40.0 sent to 重新连接v3u.cn

    如此,我们就可以利用生成器的“状态保留”机制来控制网络链接突然断开的回调补救措施了。

    所以说,协程就是一种特殊的生成器:

async def test():
pass

print(type(test()))

    您猜怎么着?

<class 'coroutine'>

  结语

    诚然,生成器和协程也并非完全是一个概念,与生成器不同的是,协程可以被另一个函数(称为调用方)恢复执行,而不是只能由生成器本身恢复执行。这使得协程可以用来实现更复杂的控制流,因为它们可以在执行时暂停并在任意时刻恢复执行。