python3-爬虫:12 协程

1. 协程,asyncio包的基础构件

进程和线程是由程序触发操作系统后执行的,而协程的操作则是由程序员开发者自己定义的

对于多线程应用过CPU通过切片的方式来切换线程间的执行,而协程只使用一个线程,由我们来规定代码块的执行顺序

从语法上来看,协程类似于生成器,需要包含yield关键字,与生成器不同的是yield通常出现在表达式的右边,可以生成值,也可以不生成值。

yield在协程中只是一种流程控制工具,使用它实现协作式多任务:把控制器交给中心调度程序,从而激活其他协程

1.1 协程的四种状态

GEN_CREATED----等待开始执行
GEN_RUNNING----解释器开始执行
GEN_SUSPENED----在yield表达式处暂停
GEN_CLOSED----执行结束

1.2 两个简单的协程案例

1.协程使用生成器函数,需要定义yield

2.yield在表达式中使用, 若果协程只需从客户那里接受数据, 那么产出的值为None, 这个值是隐式之低昂的,因为yield关键字邮编没有表达式

3.首先要调用next()方法(启动生成器),或称为预激协程,让协程向前执行到第一个yield表达式,准备好作为活跃的协程供我们使用

4.调用send方法,将值传递给yield的变量,然后恢复协程,直到运行到下一个表达式

def simple_coroutine():
    print('-> start')
    x = yield
    print('-> recived', x)

sc = simple_coroutine()
next(sc)
sc.send('666')
# 使用协程计算平均移动值
import inspect

def averager():
    total = 0.0
    count = 0
    avg = None
    while True:
        num = yield avg
        total += num
        count += 1
        avg = total/count
ag = averager()
print(next(ag))
print(ag.send(10))  # 打印avg
print(ag.send(20))

2. 显式的将异常发送给协程:

2.1 generator.throw(exc_type[, exc_value[, traceback]])

使生成器在暂停的yield表达式处抛出指定的异常,若果生成器处理了抛出的异常,name代码会向前执行到下一个yield表达式

而产出的值会成为调用generator.thow方法得到的返回值,如果生成器没有处理抛出的异常,name异常会向上冒泡,传到调用方的上下文中

2.2 generator.close()

使生成器在暂停的yield表达式处抛出CeneratorExit异常, 如果生成器没有处理这个异常,或者抛出了StopIteration异常,则调用方不会报错

如果收到GeneratorExit异常,则生成器一定不能产出值,否则解释器会抛出RuntimeErrot异常。

生成器抛出的其他异常会向上冒泡

2.1 异常处理实例

import inspect
class DemoException(Exception):
    """
    自定义异常
    """

def handle_exception():
    print('-> start')
    while True:
        try:
            x = yield
        except DemoException:
            print('run demoException')
        else:
            print('recived x:', x)
    raise RuntimeError('this line should never run')

he = handle_exception()
next(he)
he.send(10)
he.send(20)
he.throw(DemoException)
he.send(40)
he.close()

3. 使用yield from获取协程的返回值

为了得到返回值,协程必须正常终止,然后生成器对象抛出StopIteration异常,异常对象的value属性保存着返回的值。

yield from结构会在内部自动捕获StopIteration异常。

对yield结构来说, 解释器不仅会捕获StopIteration异常,还会将value属性的值变成yield from表达式的值

yield from的主要功能时打开双向通道,是最外层的调用方与最内层的生成器能够直接发送和产出值

3.1 yield from案例

import inspect
from collections import namedtuple

ResClass = namedtuple('Res', 'count average') # 声明一个具名元组 参数:元组名, '元素名1 元素名2......'

# 子生成器
def averager():
    """
    求平均值及元素个数
    :return:
    """
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return ResClass(count, average)

# 委派生成器
def grouper(storages, key):
    while True:
        # 获取averager()返回值
        storages[key] = yield from averager()

# 客户端
def client():
    process_data = {
        'body_1': [33.3,22.2,56.2,99.5,66.2,12.3,14.5],
        'body_2': [78.6,56.3,12.4,85.2,74.1,96.3,32.1]
    }
    storages = {}
    for k, v in process_data.items():
        """
        此for循环每次迭代都会新建一个grouper实例
        """
        # 获得协程
        coroutine = grouper(storages, k)


        # 预激协程
        next(coroutine) # 此时在 yield from处暂停

        for dt in v:
            coroutine.send(dt)  # 将dt传给子生成器averager,此时含在执行子生成器,storages[key] = 还未执行
            # 此循环结束后子生成器将抛出StopIteration异常并将返回的数据包含在异常对象的value中,
            # yield from可以直接爬取StopItration异常,并将异常对象的value赋值给results[key]

        # 终止协程
        coroutine.send(None)
    print(storages)

client()

4. 协程应用实例-出租车控制台

# 使用协程代替线程进行并发活动
import collections
import queue
import random

# time 事件发生的仿真时间
# proc 出租车进程的实例编号
# action 描述活动的字符串

Event = collections.namedtuple('Event', 'time proc action')

def taxi_process(proc_num, trips_num, start_time=0):
    """
    每次时间改变时,交出控制权
    :param proc_num:
    :param trips_num:
    :param start_time:
    :return:
    """
    time = yield Event(start_time, proc_num, 'leave garage')
    for i in range(trips_num):
        time = yield Event(time, proc_num, 'pick up people')
        time = yield Event(time, proc_num, 'drop off people')
    yield Event(time, proc_num, 'go home')

class SimulateTaxi(object):
    """
    模拟出租车控制台
    """
    def __init__(self, proc_map):
        # 优先级队列,保存事件,为事件排序
        self.events = queue.PriorityQueue()
        # 构建本地副本
        self.procs = dict(proc_map)

    def run(self, end_time):
        """
        排序并显示事件
        :param end_time:
        :return:
        """
        for _, taxi_gen in self.procs.items():
            leave_evt = next(taxi_gen)
            self.events.put(leave_evt)

        # 仿真系统的主循环
        simulate_time = 0
        while simulate_time < end_time:
            if self.events.empty():
                print('*** end of events ***')
                break

            # 第一个事件的发生
            current_evt = self.events.get()
            simulate_time, proc_num, action = current_evt
            print('taxi:', proc_num, ', at time: ', simulate_time, ', ', action)
            # 准备下个事件的发生
            proc_gen = self.procs[proc_num]
            next_simulate_time = simulate_time + self.compute_duration()
            try:
                next_evt = proc_gen.send(next_simulate_time)
            except StopIteration:
                del self.procs[proc_num]
            else:
                self.events.put(next_evt)
        else:
            msg = '*** end of simulation time: {} pending ***'
            print(msg.format(self.events.qsize()))

    @staticmethod
    def compute_duration():
        """
        随机产生下一个事件的发生时间
        :return:
        """
        duration_time = random.randint(1, 20)
        return duration_time

# 生成3个出租车,现在全部都没有离开garage
taxis = {i: taxi_process(i, (i + 1) * 2, i * 5) for i in range(3)}

# 模拟运行
st = SimulateTaxi(taxis)
st.run(100)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值