#Python3 从生成器yield到协程

从生成器到协程

协程的概念
  1. 协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
  2. 一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
  3. 从上述字面上的理解为,多线程的暂停与启动是操作系统来控制的,比如gil等。协程是单线程,在单个线程里面能够有能暂停,启动和向函数中去传递值的功能我们就叫做协程。
  4. python中 生成器 yield正好有这个暂停和启动的功能。我们可以思考使用生成器来作为协程使用。
生成器的基本行为
  1. 控制生成器
	def simple_gen1():
	    print('started')
	    x = yield 'running'    #调用生成器
	    print('ending',x)
	
	test1 = simple_gen1()
	
	print(next(test1))   #执行 yield之前的语句和 yield后面的“running”
	test1.send(1)			#传值1 给 yield的左边x,并执行下面的语句,当执行完毕所有语句时,
							#会包 StopIteration 异常,可用try-except语句避免报错
	
#(输出)	
Traceback (most recent call last):
  File "C:/Users/admin/PycharmProjects/untitled1/asd.py", line 9, in <module>
started
running
ending 1
    test1.send(1)
StopIteration    #如果没有下一个元素则会触发 StopIteration 异常,可用try-except语句避免报错

next 语法:
next(iterator[, default])

参数说明:
iterator – 可迭代对象
default – 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常。

def simple_gen1():
    print('started')
    x = yield 'running'  # 调用生成器
    print('ending', x)


test1 = simple_gen1()
print(next(test1))			#打印的是running,相当于函数的return的值,即yield的右边语句
next(test1)

#(输出)
	started
	Traceback (most recent call last):
	running
	  File "C:/Users/admin/PycharmProjects/untitled1/asd.py", line 9, in <module>
	ending None
	    next(test1)
	StopIteration

添加 default 参数:
test1 = simple_gen1()
print(next(test1))
next(test1,'ZXC')
print(next(test1,'ZXC'))
#(输出)
	started
	running
	ending None
	ZXC

Process finished with exit code 0
  1. 代码解释

    1. yield的右边表示生成一个值,但其实yield左边可以表示接受调用方传递的值,只不过我们一般不会书写,所以它会默认的传递None

    2. 调用next方法去启动生成器。启动之后生成器会在第一个yield的暂停住,并且生成出一个值,值是 yield表达式右边的值。

    3. 调用send方法,yield左边会接受值,并且向下执行,如果没有yield暂停则会抛出stopItertion的异常出来

    4. 通过上面的代码我们看出,生成器可以暂停,和接受值。他有协程的特征存在。

  2. 查看生成器的状态

    
    from inspect import getgeneratorstate
    def simple_gen1():
        print('started')
        x = yield 'running'
        print('receive_x:',x)
        y = yield x
        print('receive_y:', y)
    
    
    test1 = simple_gen1()
    
    print(getgeneratorstate(test1))
    
    next(test1)
    print(getgeneratorstate(test1))
    next(test1)
    print(getgeneratorstate(test1))
    try:						#跳过上面的StopIteration 异常
        next(test1)	
    except Exception:
        pass
    print(getgeneratorstate(test1))
    
    

    输出

    
    GEN_CREATED   #未激活
    started		
    GEN_SUSPENDED		#暂停中
    receive_x: None
    GEN_SUSPENDED		#暂停中
    receive_y: None
    GEN_CLOSED		#已经结束
        
    

    通过上面的代码,我们可以看到 生成器是有状态的—>
    未激活,暂停中,已经结束

  3. 运行测试:

def coro(a):
    print('res:', str(a))
    b = yield a
    print('res', str(a + b))
    c = yield b
    print('res', str(a + c))


try:
    test1 = coro(5)		#不会输出,因为是生成器对象
    next(test1)			#执行第一个yield以上的语句和右边语句,输出print的 5
    test1.send(6)		#传递值6给yield的右边b,并执行下面语句,知道遇到第二个yield,输出11
    test1.send(9) 		#传递值9给yield的右边c,并执行下面语句。输出14
except:
    pass
 
#(输出)
	res: 5
	res 11
	res 14
  1. 我们可以认为,使用yield这种关键字是使用了python的协程,yield既可以作为生成器生成值的关键字,也可以作为python里面的协程使用

  2. 协程的用法:

    1. next或者send(none)去激活协程,使他移动到yield处停止
    2. 使用 send 给协程发送值过去,协程向下执行
    3. 使用next就不会发送值,或者发送的值是none。协程向下执行
    4. 使用close可以主动的停止协程
生成器小运用
  1. 一个无限的平均值计算器
# 无限的计算平均值

# 输入:   1.  5-->5   2.  3 --->8    3. 2--->10  4. 2---->12
# 输出:   1.  5       2.  4          3. 3.333    4. 3
def avg():
    num = 0
    total = 0.0
    while 1:      # 无限的循环
        try:       # 接收一个值
            val = yield
            num += 1   	# 计算次数+1
            total += val
            avg = total / num   # 平均数等于总数/计算次数
            print('当前平均数{}'.format(avg))
        except IndexError:
            print('当前没有值')
        except Ex:
            print('停止错误')
            return 5					#return会关闭生成器

class Ex(Exception):
    pass


counter = avg()
counter.send(None)
# send 错误
while True:
    num = input('请输入数字:')
    try:
        num = float(num)
    except Exception:
        try:
            counter.throw(Ex)
        except StopIteration as e:
            print('生成器已经停止')
            print(e.value)
        continue
    counter.send(num)

#return 他可以向我们这边抛出stopiteration
#return 他可以像我们返回值
#return 关闭我们的生成器
#协程返回值是放在 stopiteration

#(输出)
请输入数字:123
当前平均数123.0
请输入数字:sfgdf'
停止错误
生成器已经停止
5						
请输入数字:12				#生成器已经关闭,再次输入就会报错了
Traceback (most recent call last):
  File "C:/Users/admin/PycharmProjects/untitled1/阿达.py", line 43, in <module>
    counter.send(num)
StopIteration
> 协程会不断的接受值,然后再yield处生成计算结果,并且暂停等待新的send值过来再循环
异常处理
  1. 协程内部发生异常之后,会向上冒泡,正如我们之前所看到的,并且会终止协程。终止之后的协程无法重新激活
  2. 我们可以使用 throw向协程内部发送异常,并且协程可以catch(抓住)异常。
class DemoException(Exception):
    pass


def test1():
    print('start')

    while True:
        try:  # 异常处理
            x = yield
        except DemoException:
            print('demo ex')
        else:
            print('receive:', x)


t = test1()

next(t)
t.send(6)
t.throw(DemoException)
t.send(9)

#(输出)
	start
	receive: 6
	demo ex
	receive: 9
>我们可以看到,协程抓住了异常并且继续正常运行
  1. close终止协程

    我们可以看到,close方法会给协程发送StopIteration异常,致使协程停止

class DemoException(Exception):
    pass

def test1():
    print('start')

    while True:
        try:
            x = yield
        except DemoException:
            print('demo ex')
        except StopIteration:
            print('asdf')
        else:
            print('receive:', x)


t = test1()
next(t)
t.send(6)
t.throw(DemoException)
t.send(9)
t.throw(StopIteration)
t.send(20)
#(输出)
	start
	receive: 6
	demo ex
	receive: 9
	asdf
	receive: 20
返回值
  1. 使用return来返回值
  2. 但是,普通的终止下,协程只会返回None
  3. 返回的值在StopIertion中
def sum():
    avg = None
    total = 0.0
    num = 0

    while True:
        r = yield avg
        if r is None:
            break
        total += r
        num += 1
        avg = total/num

    return num,avg

s1 = sum()

next(s1)
s1.send(5)
s1.send(8)
s1.send(10)

try:
    s1.send(None)
except Exception as e:
    print(e.value)
    
#(输出)
(3, 7.666666666666667)
测试
import time
import random

def consume():
    while True:
        product = yield				#新建生成器
        print('consume product:',product)
        if product == 5:
            return ('收到5')
        else:
            print("没有找到5")


def product():
    t = consume()
    next(t)			#启动生成器,如果不先启动,再进行send操作
    					#将会报错TypeError: can't send non-None value to a just-started generator
    while True:
        try:
            time.sleep(random.randint(0,1))		#产生随机数,
            t.send(random.randint(1,6))			#传递值进行验证
        except StopIteration as e:		#避免错误
            print(e.value)		
            break
            

if __name__ == "__main__":
    product()

#(输出)
	consume product: 6
	没有找到5
	consume product: 5
	收到5

模仿出租车程序

模仿出租车在120 分钟之内的随机情况
事件驱动:
1.出发
2.载客
3.出发
4.载客
。。。。。。
n.回家睡觉

根据时间进行,多辆车进行

import collections				#集合模块,提供了许多有用的集合类。
import queue						#队列模块
import random						#随机数
Event = collections.namedtuple('Event', 'time proc action')  #namedtuple给tuple起个名字,有tuple元祖的性质
											#也有dict字典性质,key-value取值,还有object对象的性质
											#第一个参数必须和变量一样,第二个参数定义数值
											#time表示出租车在什么时间做什么,
											#proc表示 那一辆出租车 即id
											#action表示事件,如出发,载客,回家

def taxi_process(ident, trips, start_time=0):    #taxi代表出租车的事件循环,默认出发时间为零分钟,
                                        # trips载客次数 ident出租车id
    time = yield Event(start_time, ident, 'leave garage')   #出发时间
    for i in range(trips):
        time = yield Event(time, ident, 'pick up passenger接客时间')    #接客时间
        time = yield Event(time, ident, 'drop off passenger送达目的')   #送达目的地下客

    yield Event(time, ident, 'going home回家')      #回家


class Simulator:

    """
    出租车数量
    """

    def __init__(self,taxis):

        self.events = queue.PriorityQueue()             #优先队列,根据的是第一个参数判断优先级排序
        self.taxis = taxis.copy()

    def run(self,end_time):             #激活出租车(生成器)
        for k, i in self.taxis.items():
            first_event = next(i)       #第一个事件
            self.events.put(first_event)    #添加事件

        sim_time = 0
        while sim_time < end_time:
            if self.events.empty():
                print('end events')
                break
            current_event = self.events.get()              #从优先队列中取事件,根据时间先后排序取值
            sim_time,taxi_id,actions = current_event        #tuple元祖拆包,比如 a,b,c = (1,2,3)
            print('taxi:',taxi_id,'at:',sim_time,'do',current_event)  #打印
            next_time = sim_time + random.randint(1,8)      #下一个事件的时间
            try:
                next_event = self.taxis[taxi_id].send(next_time)
            except StopIteration:
                del self.taxis[taxi_id]     #删除出租车id
            else:
                self.events.put(next_event)   #添加出租车id和时间 到优先队列queue.PriorityQueue中
        else:
            print('time out时间段内事件结束')

def main(taxi_num):
    taxis = dict()                 #建立一个字典,出租车id对应 出租车信息id,载客数,开始时间
    for i in range(taxi_num):       #出租车的id
        #生成出租车的对象,间隔发车,运营次数随机
        taxis[i] = taxi_process(i, random.randint(10,20), start_time=random.randint(0,10))
    sim = Simulator(taxis)      #构建对象sim,传递参数
    sim.run(120)            #调运run,运行时间120



if __name__ == '__main__':
#     taxi = taxi_process(ident=5,trips=2,start_time=7)
    # print((next(taxi)))
    # print(taxi.send(9))
    # print(taxi.send(14))
    # print(taxi.send(24))
    # print(taxi.send(34))
    # print(taxi.send(44))
    # print(taxi.send(54))
    # print(taxi.send(64))
    main(9)             #出租车数量


优化版

import collections  #集合模块,提供了许多有用的集合类。
import queue
import random


class Simulator:
    def __init__(self):
        self.events = queue.PriorityQueue()             #优先队列,根据的是第一个参数判断优先级排序
        self.taxis = {}
        self.Event = collections.namedtuple('Event', 'time proc action')
                                                #namedtuple给tuple起个名字,有tuple元祖的性质
                                                # taxi代表出租车的事件循环,默认出发时间为零分钟
                                                # trips载客次数 ident出租车id
    def taxi_process(self,ident, trips, start_time=0):
        time = yield self.Event(start_time, ident, 'leave garage:出发时间')  # 出发时间
        for i in range(trips):
            time = yield self.Event(time, ident, 'pick up passenger:接客时间')  # 接客时间
            time = yield self.Event(time, ident, 'drop off passenger:送达目的')  # 送达目的地下客
        yield self.Event(time, ident, 'going home回家')  # 回家

    def run(self,end_time):             #激活出租车(生成器)
        for k, i in self.taxis.items():
            first_event = next(i)       #第一个事件
            self.events.put(first_event)    #添加事件

        sim_time = 0
        while sim_time < end_time:
            if self.events.empty():         #Queue.empty()如果队列为空,返回True,反之False,
                                            # 可设置出租车数量为零时触发
                print('end events')
                break
            current_event = self.events.get()              #从优先队列中取事件,根据时间先后排序取值
            sim_time,taxi_id,actions = current_event        #tuple元祖拆包,比如 a,b,c = (1,2,3)
            print('taxi:',taxi_id,'at:',sim_time,'do',current_event)  #打印
            next_time = sim_time + random.randint(1,8)      #下一个事件的时间
            try:
                next_event = self.taxis[taxi_id].send(next_time)    #从taxis字典里找到对应的出租车进行send
                                                                    #send 给协程发送值过去,协程向下执行
            except StopIteration:
                del self.taxis[taxi_id]     #删除出租车id
            else:
                self.events.put(next_event)   #添加出租车id和时间 到优先队列queue.PriorityQueue中
        else:
            print('time out时间段内事件结束')

    def main(self,taxi_num):
        for i in range(taxi_num):  # 出租车的id
             # 生成出租车的对象,间隔发车,运营次数随机
            self.taxis[i]=self.taxi_process(i, random.randint(10, 20), start_time=random.randint(0, 10))
        self.run(120)  # 调运run,运行时间120

if __name__ == '__main__':
    ss  = Simulator()
    ss.main(2)             #出租车数量
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值