ITPub博客

首页 > 数据库 > 数据库开发技术 > python 并发编程-协程篇(1)-使用yield实现协程一些实践说明

python 并发编程-协程篇(1)-使用yield实现协程一些实践说明

原创 数据库开发技术 作者:程序员小钟同学 时间:2021-05-28 17:37:04 0 删除 编辑

协程和python中的生成器关系

早期的没有标准的协程库的时候,tornado内部就是通过自定义的yield的方式来实现协程的。

那说明我们的协程和yield的生成器是有一定的关系,什么是生成器呐?

如果你可以,可以回顾一下之前我梳理的一些知识点。

python面向对象编程篇(16):你应该需要掌握了解的-迭代器和生成器
python面向对象编程篇(17):生成器之 yield  和yield from 表达式的实践

这里也无妨重复再多说一下:

python种的生成器和迭代器是息息相关滴,而迭代器又和可迭代对象等有关系。

这里补充一下概念理解:

  • 迭代器(Iterator):在Python中,支持迭代器协议就是实现对象的__iter__()和next()方法,iter()和next()方法是实现迭代器最基本的方法,所以也可以说是:迭代器就是一个实现了__iter__()和__next__()的对象。
  - 通过iter()方法获得了list的迭代器对象
  - 通过和next()访问list中元素对象
  - 如果容内已无没有可访问的元素,继续调用next()方法将会抛出一个StopIteration异常终止迭代器
  - for语句已封装了关于__iter__()和next()自动调用
❝

PS:通常我们对一个iterable用for ... in ...进行迭代的时候实际是先通过调用iter()方法得到一个iterator,可迭代的对象A,,然后循环地调用可迭代对象A的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为迭代器循环结束的标志。

  • 可迭代对象(Iterable):实现了__iter__()方法的对象,通过调用iter()方法可以获得一个迭代器(Iterator)。
- 1)列表、元组、集合、字典、字符串等等之类的对象被称为可迭代对象
- 2)任何你可以循环的对象都是可迭代对象
  • 生成器(generator):一个普通函数内部包含yield的函数通常就是一个生成器,该函数调用的返回值是一个生成器对象,但是此时对函数的调用,并不会执行函数内部的代码,而是需要等调用了next()才回去执行!
  - yield是对生成器更优雅一种封装,所以它是一个特殊的迭代器。
  
  - yield 是生成器实现__next__()方法的关键。它作为生成器执行的暂停恢复点,可以对yield表达式进行赋值,也可以将yield表达式的值返回。
  
  - __next__()方法被调用时,既可以计算返回值,还可以记住当前函数已执行的状态,下次__next__()的调用,会从上次停止的地方开始。
  
  - yield可以暂停一个函数并返回此时的中间结果。该函数将保存执行环境并在下一次恢复。
  
  (这里我们就可以大致的理解到,关于协程是如何的异步,当调用next的时候可以记录当前的状态,等到下次next()的时候IO完成的时候再恢复)
  
  • 生成器有两种表示:

    • 生成器表达式
    • 生成器函数

使用生成器:yield实现协程的示例

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    yield "你好我是你三大爷!"

# 生产一个对象
opj =ceshi()
# 吧对象丢到next
print('第1次调用:',next(opj))
print('第2次调用:',next(opj))
print('第3次调用:',next(opj))
print('第4次调用:',next(opj))

输出结果


StopIteration
第1次调用: 你好我是你大爷!
第2次调用: 你好我是你二大爷!
第3次调用: 你好我是你三大爷!
Traceback (most recent call last):
 print('第4次调用:',next(opj))
StopIteration

上面的示例也可以直接的而是要FOR进行生成器的调用:

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    yield "你好我是你三大爷!"

# # 生产一个对象
# opj =ceshi()
# # 吧对象丢到next
# print('第1次调用:',next(opj))
# print('第2次调用:',next(opj))
# print('第3次调用:',next(opj))
# print('第4次调用:',next(opj))

for i in ceshi():
    print('FOR:方式',i)

异步网络执行的简单示例:

方式1:
# 是一个生成器的对象
import requests

def ceshi():
    yield requests.get("http://www.baidu.com").status_code
    yield requests.get("http://www.taobao.com").status_code
    yield requests.get("http://www.qq.com").status_code

# 生产一个对象
opj =ceshi()
# 吧对象丢到next
print('第1次调用:',next(opj))
print('第2次调用:',next(opj))
print('第3次调用:',next(opj))

方式2:
# 是一个生成器的对象
import requests

def ceshi():
    yield requests.get("http://www.baidu.com").text[10:50]
    yield requests.get("http://www.taobao.com").text[10:50]
    yield requests.get("http://www.qq.com").text[10:50]

# 生产一个对象
opj =ceshi()
def run(opj):
    # 启动了生成器  调用了
    n=0
    while n<=2:
        n=n+1
        #send一些 value 发回到暂停执行的函数
        resutl =opj.send(None)
        print(resutl)
    opj.close()

run(opj)

输出结果:

html>
<!--STATUS OK--><html> <head><met
                                        
html>
<html lang="zh-CN">

<head>
  <tit

上面网络请求:发起网络的请求其实会遇到IO阻塞,按以前我们的同步模式的话,此时一般需要等待这个IO阻塞完成才会下一步,但是现在生成器不一样了,我丢出去请求后,我就不管了,直接的返回了!所以一个线程下我就可以同时跑三个网络的请求了!!!!!且是很快的丢出去!不等待!

❝

PS:

  • 第一次调用send()激活生成器时,value值必须为None,因为当前还没有yield表达式可以接收参数。
  • yield表达式有一个返回值,send方法的作用就是控制这个返回值,send的参数就是yield表达式的返回值
  • 激活生成器的方法有:next和send,其中send的主要给我们的已暂停执行的函数中,传入值。

查看生成器状态

既然我们的生成器可以暂停和可以继续恢复,那肯定是有所谓的状态机附体。那我们的生成器有哪几种状态呐?生成器的四种状态,分别是:

  • GEN_CREATED:等待执行,即还没有进入协程
  • GEN_RUNNING:解释器执行(只有在使用多线程时才能查看到他的状态,而协程是单线程的)
  • GEN_SUSPENDED:在yield表达式处暂停(协程在暂停等待的时候的状态)
  • GEN_CLOSED:执行结束(协程执行结束了之后的状态)

那如何知道我们的生成器的当前的状态呐?

通过inspect库中的getgeneratorstate的方法可以查看:

import requests
from inspect import getgeneratorstate  #一定要导入

def ceshi():
    yield requests.get("http://www.baidu.com").status_code
    yield requests.get("http://www.taobao.com").status_code
    yield requests.get("http://www.qq.com").status_code

# 生产一个对象
opj =ceshi()
def run(opj):
    # 启动了生成器  调用了
    n=0
    while n<=2:
        n=n+1
        print("生成器状态为--激活前:{0}".format(getgeneratorstate(opj)))
        # 开始激活生成器
        resutl =opj.send(None)
        print("生成器状态为--激活后:{0}".format(getgeneratorstate(opj)))
        print(resutl)
    print("生成器状态为--关闭前:{0}".format(getgeneratorstate(opj)))
    opj.close()
    print("生成器状态为--关闭后:{0}".format(getgeneratorstate(opj)))

run(opj)

输出结果:

生成器状态为--激活前:GEN_CREATED
生成器状态为--激活后:GEN_SUSPENDED
200
生成器状态为--激活前:GEN_SUSPENDED
生成器状态为--激活后:GEN_SUSPENDED
200
生成器状态为--激活前:GEN_SUSPENDED
生成器状态为--激活后:GEN_SUSPENDED
200
生成器状态为--关闭前:GEN_SUSPENDED
生成器状态为--关闭后:GEN_CLOSED

总结:从上面简单示例我们可以看到单进程单线程的并发的异步处理,有了协程的影子!生成器和协程大致雏形上类似。

生成器:yield和 yield from xxxxxx

1)区别:


- yield :是返回一个元素

- yield from xxxxxx:返回是一个新的生成器对象

- yield from 后面可以返回:生成器 、元组、 列表、等可迭代对象

- yield from iterable本质上等于 for item in iterable: yield item 

2)yield from xxxxxx 要解决的问题:

加入我们的生成器中存在对应条件且直接终止我们的生成器的迭代过程的话:会怎么样?

1):使用for循环我们的迭代的情况

❝

PS: for可以自动帮处理StopIteration异常

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    return "你好我是你三大爷!"

for i in ceshi():
    print('FOR:方式',i)

输出的结果:

FOR:方式 你好我是你大爷!
FOR:方式 你好我是你二大爷!

2):使用next循环我们的迭代的情况

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    return "你好我是你三大爷!"

opj= ceshi()

# 吧对象丢到next
print('第1次调用:',next(opj))
print('第2次调用:',next(opj))
print('第3次调用:',next(opj))

输出结果:


第1次调用: 你好我是你大爷!
 
第2次调用: 你好我是你二大爷!
 File "D:/code/python/local_python/xxxxx.py", line 28, in <module>
Traceback (most recent call last):
    print('第3次调用:',next(opj))
StopIteration: 你好我是你三大爷!

3)加上异常捕获的情况:

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    return "你好我是你三大爷!"

opj= ceshi()

# 吧对象丢到next
try:
    print('第1次调用:',next(opj))
    print('第2次调用:',next(opj))
    print('第3次调用:',next(opj))
except StopIteration as e:
    print(e.value)

输出结果:

第1次调用: 你好我是你大爷!
第2次调用: 你好我是你二大爷!
你好我是你三大爷!

从上面的情况可以看出:

  • for 方式的迭代 无法返回我们最后return的值
  • next+异常的捕获的方式,可以获取最后return的值

如何和改造一下生成器的对象可以使用for迭代的时候,也可以获取我们最后return的值呐?

之前其实也有详细讲述过,可以参考:

python面向对象编程篇(17):生成器之 yield 和yield from 表达式的实践

def ceshi():
    yield "你好我是你大爷!"
    yield "你好我是你二大爷!"
    return "你好我是你三大爷!"



# 本质上这个也还是一个生成器,因为有yield
def ceshi_yield_form(generator):
     result =yield from generator
     # 获取到了我们的return的值
     print('我们的return的值:',result)
# 吧对象丢到next
opj= ceshi()

for i in ceshi_yield_form(opj):
    print(i)

输出:

你好我是你大爷!
你好我是你二大爷!
我们的return的值: 你好我是你三大爷!

上面的例子:

  • ceshi_yield_form 其实无非是对对一个生成器的嵌套
  • yield_form 主要解决的问题是生成器的嵌套问题

以上仅为个人的总结认知,如有如有不当之处,还望各位大佬指正!

❝

个人其他博客地址

❝

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

小钟同学 | 文 【原创】| QQ:308711822

  • 1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
  • 2:关于文章内容,有部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。


原文作者:程序员小钟同学;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-02-20

  • 博文量
    224
  • 访问量
    111765