python 终止协程和异常处理

本文探讨了Python协程中异常的处理机制,包括未处理异常的影响、如何使用close和throw方法控制协程,以及如何在协程中进行异常处理和清理工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

协程中未处理的异常会向上抛出,传给 next 函数或 send 方法的调用方(即触发协程的对象)。下面示例举例说明如何使用之前博客(https://blog.youkuaiyun.com/MZP_man/article/details/100555244?spm=1001.2014.3001.5502)示例中由装饰器定义的 averager 协程。

未处理的异常会导致协程终止

from inspect import getgeneratorstate
from functools import wraps


def coroutine(func):
    """装饰器:向前执行到第一个`yield`表达式,预激`func`"""

    @wraps(func)
    def primer(*args, **kwargs):  # 把被装饰的生成器函数替换成这里的 primer 函数;调用 primer 函数时,返回预激后的 生成器
        gen = func(*args, **kwargs)  # 调用被装饰的函数,获取生成器对象。
        next(gen)  # 预激生成器。
        return gen  # 返回生成器。

    return primer


@coroutine  # 把装饰器应用到 averager 函数上
def averager():  # 实现求平均值
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count


if __name__ == '__main__':
    coro_avg = averager()  # 使用 @coroutine 装饰器装饰的 averager 协程,可以立即开始发送值。
    print(coro_avg.send(10))
    # 10.0
    print(coro_avg.send(30))
    # 20.0
    print(coro_avg.send('spam'))  # 发送的值不是数字,导致协程内部有异常抛出。
    #   File "C:/myFiles/company_project/xbot/mytest/test.py", line 34, in <module>
    #     print(coro_avg.send('spam'))  # 发送的值不是数字,导致协程内部有异常抛出。
    #   File "C:/myFiles/company_project/xbot/mytest/test.py", line 24, in averager
    #     total += term
    # TypeError: unsupported operand type(s) for +=: 'float' and 'str'
    print(coro_avg.send(60))  # 由于在协程内没有处理异常,协程会终止。如果试图重新激活协程,会抛出StopIteration异常。
    # StopIteration                             Traceback (most recent call last)
    # <ipython-input-7-23a7fcf06c51> in <module>
    # ----> 1 coro_avg.send(60)
    # 
    # StopIteration:

tips: 出错的原因是,发送给协程的 ‘spam’ 值不能加到 total 变量上。

上面示例,暗示了终止协程的一种方式:发送某个哨符值,让协程退出。内置的 NoneEllipsis 等常量经常用作哨符值。Ellipsis优点是,数据流中不太常有这个值。我还见过有人把 StopIteration 类(类本身,而不是实例,也不抛出)作为哨符值;也就是说,是像这样使用的:my_coro.send(StopIteration)

Python 2.5 开始,客户代码可以在生成器对象上调用两个方法显式地把异常发给协程。这两个方法是 throwclose

  • generator.throw(exc_type[, exc_value[, traceback]])
    致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw 方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上抛,传到调用方的上下
    文中。

  • generator.close()
    致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上抛,传给调用方。

tips:
生成器对象方法的官方文档深藏在 Python 语言参考手册中,参见“6.2.9.1. Generator-iterator methods”(https://docs.python.org/3/reference/expressions.html#generator-iterator-methods)。

下面举例说明如何使用 closethrow 方法控制协程。

# -*- coding: utf-8 -*-
from inspect import getgeneratorstate


class DemoException(Exception):
    """为这次演示定义的异常类型。"""


def demo_exc_handling():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:  # 特别处理 DemoException 异常。
            print('*** DemoException handled. Continuing...')
        else:  # 如果没有异常,那么显示接收到的值。
            print('-> coroutine received: {!r}'.format(x))

    # 这一行永远不会执行。因为只有未处理的异常才会中止无限循环,而
    # 一旦出现未处理的异常,协程会立即终止
    raise RuntimeError('This line should never run.')


if __name__ == '__main__':
    exc_coro = demo_exc_handling()
    next(exc_coro)

    # -> coroutine started
    exc_coro.send(11)
    # -> coroutine received: 11
    exc_coro.send(22)  # 正常send
    # -> coroutine received: 22

    print(exc_coro.throw(DemoException))  # 把 DemoException 异常传入 demo_exc_handling 不会导致协程中止
    # *** DemoException handled. Continuing...
    print(getgeneratorstate(exc_coro))
    # GEN_SUSPENDED

    exc_coro.throw(ZeroDivisionError)  # 如果无法处理传入的异常,协程会终止
    # Traceback (most recent call last):
    #   File "C:/myFiles/company_project/xbot/mytest/test.py", line 39, in <module>
    #     exc_coro.throw(ZeroDivisionError)  # 如果无法处理传入的异常,协程会终止
    #   File "C:/myFiles/company_project/xbot/mytest/test.py", line 13, in demo_exc_handling
    #     x = yield
    # ZeroDivisionError
    print(getgeneratorstate(exc_coro))
    # GEN_CLOSED

    exc_coro.close()
    print(getgeneratorstate(exc_coro))
    # GEN_CLOSED

如果不管协程如何结束都想做些清理工作,要把协程定义体相关的代码放入 try/finally 块中, 如下示例。

# -*- coding: utf-8 -*-
from inspect import getgeneratorstate


class DemoException(Exception):
    """为这次演示定义的异常类型。"""


def demo_finally():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('*** DemoException handled. Continuing...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        print('-> coroutine ending')



<think>好的,我现在需要帮助用户了解如何使用Python协程进行数据处理的最佳实践示例。首先,用户提到了几个引用,涉及asyncio、协程的装饰器以及gevent库。我需要结合这些引用来给出回答。 首先,我应该先解释协程在数据处理中的优势,比如异步处理I/O密集型任务,提高效率。然后分点列出最佳实践,比如选择合适的库、分离任务、错误处理等。每个实践下给出具体的例子,比如使用asyncio的async/await语法,或者gevent的协程池。 接下来,我需要考虑用户可能的疑问,比如如何选择asyncio还是gevent。根据引用中的信息,asyncio是Python标准库,适合网络网站服务,而gevent适合需要保持简单阻塞代码逻辑的场景。这点需要明确说明。 然后,给出示例代码。需要确保代码符合用户提到的语法要求,比如行内数学表达式用$...$,独立公式用$$...$$,但用户的问题是关于Python协程的,可能数学公式较少,但如果有的话需要注意格式。 示例部分可能需要展示asynciogevent两种方式,因为用户引用了两者的内容。例如,用asyncio处理HTTP请求,用gevent进行并发数据处理。每个示例后解释代码结构关键点。 还要注意错误处理,比如在协程中使用try-except块,或者设置超时,这在引用[2]中提到过终止协程异常处理,所以需要包含这部分内容。 最后,生成相关问题,引导用户进一步学习,比如性能优化、与多线程对比、大数据处理中的应用等。 需要检查是否符合用户的所有要求:使用中文,结构清晰,分步骤,引用正确。引用标识要放在引用内容的末尾,比如在提到asyncio基础时添加[^1],提到装饰器时引用[^2],提到gevent时引用[^3]。 可能还需要注意用户提到的系统级指令,比如数学格式,但问题主要涉及代码,所以可能这部分内容不多。不过如果有任何数学表达式,比如提到数据处理中的某个公式,要正确使用$符号。 总结步骤: 1. 介绍协程在数据处理中的优势。 2. 分点列出最佳实践,结合引用内容。 3. 提供asynciogevent的示例代码。 4. 强调错误处理资源管理。 5. 生成相关问题。 确保每个部分都自然引用对应的引用源,语言简洁明了,结构清晰。</think>### Python协程在数据处理中的最佳实践与示例 #### 一、协程的优势与适用场景 协程通过异步非阻塞的特性,特别适合处理 **I/O密集型任务**(如文件读写、网络请求、数据库查询)[^3]。相较于多线程/多进程,协程的上下文切换成本更低,且能更高效地利用单核CPU资源[^1]。 --- #### 二、最佳实践与步骤 1. **选择合适的协程库** - 若需标准库支持或需要底层控制,优先选择 **`asyncio`** - 若需快速改造传统阻塞代码,推荐使用 **`gevent`** [^3] 2. **分离任务粒度** - 将数据拆分为独立处理单元(如分块读取文件、分页查询数据库) - 使用协程池限制并发量,避免资源耗尽: ```python import asyncio async def process_data_chunk(chunk): # 模拟数据处理(如调用API或计算) await asyncio.sleep(1) return chunk.upper() async def main(): chunks = ["data1", "data2", "data3"] tasks = [process_data_chunk(chunk) for chunk in chunks] results = await asyncio.gather(*tasks) print(results) # 输出: ['DATA1', 'DATA2', 'DATA3'] asyncio.run(main()) ``` 3. **错误处理与重试机制** ```python async def safe_processing(chunk): try: async with asyncio.timeout(5): # 设置超时 return await process_data_chunk(chunk) except Exception as e: print(f"处理失败: {chunk}, 错误: {str(e)}") return None ``` 4. **结合异步生态工具** - 数据库:`asyncpg` (PostgreSQL), `aiomysql` - HTTP客户端:`aiohttp`, `httpx` - 消息队列:`aio-pika` (RabbitMQ) --- #### 三、典型示例对比 1. **使用`asyncio`处理HTTP API数据** ```python import aiohttp async def fetch_data(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.json() async def aggregate_data(urls): tasks = [fetch_data(url) for url in urls] return await asyncio.gather(*tasks) # 使用示例 urls = ["https://api.example.com/data/1", "https://api.example.com/data/2"] results = asyncio.run(aggregate_data(urls)) ``` 2. **使用`gevent`进行批量文件处理** ```python from gevent import monkey; monkey.patch_all() import gevent.pool def process_file(file_path): with open(file_path) as f: return f.read().upper() pool = gevent.pool.Pool(10) # 限制并发协程数 files = ["file1.txt", "file2.txt"] results = pool.map(process_file, files) ``` --- #### 四、关键注意事项 1. **避免CPU密集型操作**:协程不适合大量计算任务,此时应结合多进程 2. **控制并发量**:使用信号量或连接池限制资源使用 ```python semaphore = asyncio.Semaphore(10) # 最大10并发 async def limited_task(): async with semaphore: await process_data() ``` 3. **监控与调试**:使用`asyncio.run()`的调试模式或`gevent.monitor` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值