深入理解Python协程:async def、async for、await、yield详解

前言

在现代编程中,异步编程已成为提高程序效率和性能的重要方式。

Python 作为一种流行的编程语言,自然也提供了强大的异步编程支持。

本文将详细介绍 Python 中的协程,以及 async def、async for、await 和 yield 等关键字的使用。


协程简介

协程是一种比传统函数更高级的控制结构。

它们在一个过程中暂停,然后在另一个地方恢复执行

协程可以在程序的多个点之间切换,从而实现并发执行,而无需多线程或多进程的开销。


协程 vs 线程

与线程不同,协程由程序员手动控制其切换。

线程在操作系统级别进行调度,可能导致频繁的上下文切换开销。

协程则由 Python 解释器调度,开销更低,且不会发生竞争资源的问题。


一、async def 和 await

Python 3.5及之后版本中,引入了 asyncawait 关键字,使得定义和调用协程变得更为简洁和直观。

async def

async def 用于定义一个协程函数。

与普通函数不同,协程函数在调用时不会立即执行,而是返回一个协程对象,直到被 await 调用时才会运行。

import asyncioasync def my_coroutine():print("Hello")await asyncio.sleep(1)print("World")# 调用协程函数
coroutine = my_coroutine()

await

await 用于暂停协程的执行,等待另一个协程完成,并获取其结果。

await 后面必须跟随一个可等待对象。

协程、Future对象或其他实现了 __await__方法的对象。

async def main():print("Start")await my_coroutine()print("End")# 运行主协程
# asyncio.run(main())
  • 在上面的示例中,await my_coroutine() 会暂停 main 的执行,直到 my_coroutine 运行结束。
  • asyncio.run 这个函数是 Python 3.7 之后才有的特性。
  • 可以让 Python 的协程接口变得非常简单,一个好的编程规范是,asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run

二、async for 和 async with

Python 3.6 引入了 async for 和 async with,使得异步迭代和上下文管理变得更加方便。

async for

async for 用于异步迭代可等待对象的异步迭代器。

它的工作方式类似于普通的 for 循环,但可以在异步环境中使用。

class AsyncIterator:def __init__(self):self.count = 0async def __aiter__(self):return selfasync def __anext__(self):if self.count < 5:self.count += 1return self.countelse:raise StopAsyncIterationasync def async_for_example():async for number in AsyncIterator():print(number)asyncio.run(async_for_example())

async with

async with 用于异步上下文管理器。

它的作用与 with 语句类似,但适用于异步环境,确保在异步操作前后执行特定的设置和清理操作。

class AsyncContextManager:async def __aenter__(self):print("Enter context")return selfasync def __aexit__(self, exc_type, exc, tb):print("Exit context")async def async_with_example():async with AsyncContextManager() as manager:print("Inside context")asyncio.run(async_with_example())

三、yield 和 yield from

yield 和 yield from 是生成器相关的关键字。

但它们也可以用于协程中,尤其是在生成器协程(Python 3.3之前的异步实现)中。

yield

yield用于定义生成器函数。

生成器函数在每次 yield 语句处暂停,并在下次调用 next() 方法时继续执行。

def simple_generator():yield 1yield 2yield 3for value in simple_generator():print(value)

yield from

yield from 用于委派生成器,允许一个生成器将部分操作委托给另一个生成器。

def generator1():yield 1yield 2def generator2():yield from generator1()yield 3for value in generator2():print(value)

在异步编程中,yieldyield from 也可以用于异步生成器和异步迭代器。


四、create_task 和 gather

asyncio.create_task 和 asyncio.gather 是两个重要的工具,用于并发运行多个协程。

asyncio.create_task

asyncio.create_task 用于将协程包装成任务,使其能够在事件循环中并发运行。

import asyncioasync def task1():await asyncio.sleep(1)print("Task 1 completed")async def task2():await asyncio.sleep(2)print("Task 2 completed")async def main():task1_task = asyncio.create_task(task1())task2_task = asyncio.create_task(task2())# 等待所有任务完成await task1_taskawait task2_taskasyncio.run(main())

在这个示例中,我们创建了两个任务 task1task2
并通过 asyncio.create_task 将它们包装成可并发运行的任务。
然后,我们使用 await 等待所有任务完成。

asyncio.gather

asyncio.gather 用于并行运行多个协程,并收集它们的结果。

它比 create_task 更加方便,尤其是当我们需要同时运行多个任务并获取它们的结果时。

import asyncioasync def task1():await asyncio.sleep(1)print("Task 1 completed")return "Result 1"async def task2():await asyncio.sleep(2)print("Task 2 completed")return "Result 2"async def main():results = await asyncio.gather(task1(), task2())print(results)asyncio.run(main())

在这个示例中,asyncio.gather 并行运行 task1task2
并在所有任务完成后返回一个包含结果的列表,这样我们可以更方便地管理和处理多个协程任务。

异常处理与取消任务

在实际应用中,协程可能会抛出异常,或者需要在执行过程中取消某些任务。

我们可以通过 asyncio.gatherreturn_exceptions 参数来收集异常,同时也可以使用 cancel 方法来取消任务。

import asyncioasync def worker_1():await asyncio.sleep(1)return 1async def worker_2():await asyncio.sleep(2)return 2 / 0async def worker_3():await asyncio.sleep(3)return 3async def main():task_1 = asyncio.create_task(worker_1())task_2 = asyncio.create_task(worker_2())task_3 = asyncio.create_task(worker_3())await asyncio.sleep(2)task_3.cancel()res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)print(res)# 输出: [1, ZeroDivisionError('division by zero'), CancelledError()]
asyncio.run(main())
  • 在这个示例中,我们创建了三个任务 worker_1、worker_2 和 worker_3
  • 其中 worker_2 会抛出一个除零异常,而 worker_3 会在执行过程中被取消。
  • 我们使用 asyncio.gather 来收集所有任务的结果,并通过 return_exceptions=True 参数捕获所有异常。
  • 最终的输出包含了正常完成任务的结果、抛出的异常以及取消任务的状态。

通过 asyncio.create_taskasyncio.gather,我们可以有效地并行运行多个协程任务,极大地提高程序的并发性能。
这在处理大量I/O操作或需要同时执行多个独立任务的场景中尤为重要。


五、并发度控制asyncio.Semaphore

asyncio.SemaphorePythonasyncio 模块中的一个重要工具,用于控制并发任务的数量。

它在处理大量并发操作时尤为重要,尤其是在需要限制同时运行的任务数量以避免过载或超出限制的场景中。

asyncio.Semaphore 是一种异步互斥量,允许在同一时间内有固定数量的任务访问某个资源。

它可以帮助你在异步编程中控制并发级别,防止系统过载或超出外部服务的限制。

工作原理

1. 初始化:

semaphore = asyncio.Semaphore(value)
  • value 表示信号量的初始值,也即允许同时运行的任务数量。
  • 默认值是 1,表示互斥量(类似于锁)。

2. 获取信号量:

async with semaphore:# 受控的代码块
  • async with semaphore 是异步上下文管理器,获取信号量(即允许继续执行的许可证)并进入受控的代码块。
  • 当代码块执行完毕时,信号量会自动释放,使其他任务能够继续执行。

3. 释放信号量:

  • async with 语句块结束时,信号量会自动释放。
  • 这确保了每个获取信号量的操作都有一个匹配的释放操作。
使用场景
  • 控制并发任务数量:

使用 asyncio.Semaphore 来限制同时进行的任务数量。例如,当处理大量网络请求时,控制并发度可以防止超出 API 的速率限制或避免过载。

  • 避免资源争用:

当多个任务访问共享资源时,信号量可以确保资源访问的有序性和一致性,避免资源争用问题。

示例

以下是一个简单的示例,展示了如何使用 asyncio.Semaphore 来限制同时运行的任务数量:

import asyncioasync def worker(semaphore, worker_id):async with semaphore:print(f"Worker {worker_id} is working")await asyncio.sleep(1)print(f"Worker {worker_id} has finished")async def main():semaphore = asyncio.Semaphore(3)  # Limit concurrency to 3tasks = [worker(semaphore, i) for i in range(10)]await asyncio.gather(*tasks)if __name__ == "__main__":asyncio.run(main())

在这个例子中,Semaphore(3) 限制了最多同时运行 3worker 任务。

当有更多任务时,它们必须等待直到有信号量可用。

因此上述代码的运行日志为:

Worker 0 is working
Worker 1 is working
Worker 2 is working
Worker 0 has finished
Worker 1 has finished
Worker 2 has finished
Worker 3 is working
Worker 4 is working
Worker 5 is working
Worker 3 has finished
Worker 4 has finished
Worker 5 has finished
Worker 6 is working
Worker 7 is working
Worker 8 is working
Worker 6 has finished
Worker 7 has finished
Worker 8 has finished
Worker 9 is working
Worker 9 has finished

总结

asyncio.Semaphore 是控制异步操作并发度的一个强大工具,它能够有效管理任务并发,避免超载和资源争用。

理解和正确使用信号量可以帮助你在异步编程中实现更高效、更可靠的代码。


六、协程与生成器的关系

协程与生成器有很多相似之处,都能够在函数执行过程中暂停并恢复,但它们的设计目的和使用场景有所不同。

相似之处

暂停与恢复:两者都可以在执行过程中暂停,并在之后恢复。

关键字:协程使用 await 暂停执行,生成器使用 yield 暂停执行。

不同之处

生成器:主要用于生成一系列值,常用于迭代。

协程:主要用于处理异步操作,管理并发任务。

生成器:使用 yield 关键字。

协程:使用 async def 定义,await 关键字用于暂停。

控制流:

生成器:由调用方(迭代器)控制。

协程:由事件循环控制。

结合使用

在某些情况下,可以结合使用生成器和协程。

例如,在异步生成器中使用 yield 生成值,并使用 await 等待异步操作完成。

async def async_generator():for i in range(5):await asyncio.sleep(1)yield iasync def main():async for value in async_generator():print(value)asyncio.run(main())

在这个示例中,我们定义了一个异步生成器函数 async_generator,它每秒生成一个值,并在主协程中异步迭代这些值。


七、实际应用场景

异步编程在实际中有广泛的应用,尤其是在处理I/O密集型任务时,如网络请求、文件操作等。

通过异步编程,可以在等待I/O操作时执行其他任务,从而提高程序的并发性能。

异步网络请求

import aiohttpasync def fetch(session, url):async with session.get(url) as response:return await response.text()async def main():async with aiohttp.ClientSession() as session:html = await fetch(session, 'http://example.com')print(html)asyncio.run(main())

在这个示例中,我们使用 aiohttp 库进行异步网络请求,大大提高了效率。

异步文件操作

import aiofilesasync def read_file(filename):async with aiofiles.open(filename, 'r') as f:contents = await f.read()print(contents)asyncio.run(read_file('example.txt'))

通过 aiofiles 库,我们可以实现异步的文件读写操作,提高文件I/O操作的性能。

异步大模型流式服务

大模型(如GPT-4)相关的应用中,流式服务是一种常见的需求。
通过异步编程,可以实现高效的流式数据处理,提高服务响应速度。

import asyncioasync def stream_handler(reader, writer):while True:data = await reader.read(100)if not data:breakprint(f"Received: {data.decode()}")response = f"Echo: {data.decode()}"writer.write(response.encode())await writer.drain()writer.close()await writer.wait_closed()async def main():server = await asyncio.start_server(stream_handler, '127.0.0.1', 8888)async with server:await server.serve_forever()asyncio.run(main())

在这个示例中,我们使用 asyncio 库创建了一个简单的流式服务。

客户端发送的数据会被接收并立即返回给客户端,实现了基本的流式处理功能。


八、总结

Python的协程和异步编程为开发高效的并发程序提供了强大的工具。

通过 async def、await、async for 和 async with 等关键字,我们可以编写简洁、易读的异步代码。

理解和熟练应用这些关键字,将大大提高你的编程效率和程序性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于功能基团的3D分子生成扩散模型 - D3FG 评测

D3FG 是一个在口袋中基于功能团的3D分子生成扩散模型。与通常分子生成模型直接生成分子坐标和原子类型不同&#xff0c;D3FG 将分子分解为两类组成部分&#xff1a;官能团和连接体&#xff0c;然后使用扩散生成模型学习这些组成部分的类型和几何分布。 一、背景介绍 D3FG 来源…

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…

如何提升企微CRM系统数据的准确性?5大核心策略详解

在数字化客户管理时代&#xff0c;企微CRM管理系统已成为企业连接客户的核心平台。但据统计&#xff0c;73%的企业因数据质量问题导致客户分析失真、营销效果下降。本文将深入解析影响数据准确性的关键因素&#xff0c;并提供可落地的优化方案&#xff0c;帮助企业在企微CRM软件…

Unity轻松实现麦克风录音与播放

文章目录 概要录音&#xff0c;播放音频注意事项参考 概要 之前有想写一个音乐播放的器的音频功能&#xff0c;一直没做&#xff0c;最近突然想写&#xff0c;就写了 录音&#xff0c;播放 在语言模型中&#xff0c;编码器和解码器都是由一个个的 Transformer 组件拼接在一起…

七牛云图片上传 前后端全过程

相关网址&#xff1a;七牛开发者中心 相关网站&#xff1a; 七牛开发者中心 上传流程概述 后端生成上传凭证&#xff1a;服务器端使用七牛云 SDK 生成上传凭证&#xff08;uptoken&#xff09;前端获取凭证&#xff1a;前端通过 API 向后端请求上传凭证前端上传图片&#xff1…

2025年AI生成PPT平台推荐榜单:五大智能工具革新演示创作体验

在数字化办公飞速发展的当下&#xff0c;AI生成PPT平台已成为职场人士、教育工作者和创意人群提升效率的利器。这些平台凭借先进的人工智能技术&#xff0c;打破传统PPT制作的局限&#xff0c;为用户带来便捷、高效且充满创意的制作体验。经过多维度测评&#xff0c;2025年AI生…

PHP框架在内容管理系统开发中的优势:效率、安全与扩展性!

在当今快节奏的Web开发环境中&#xff0c;内容管理系统&#xff08;CMS&#xff09;已成为企业和个人建立动态网站的核心工具。传统的手工编码开发方式在面对复杂业务逻辑、频繁迭代和安全要求时往往力不从心。而PHP框架&#xff08;如Laravel、ThinkPHP、Symfony&#xff09;的…

云原生安全实践:CI/CD流水线集成DAST工具

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 ——从零掌握DevSecOps中的动态安全测试 一、基础概念 1. DevSecOps DevSecOps 是将安全性&#xff08;Security&#xff09;融入开发&#xff08;Dev&am…

【C语言】基础知识框架补充

前文主要介绍了C语言从零开始学习的基本框架与基础知识导览&#xff0c;本文主要补充此前未提及的学习内容&#xff0c;给有意精进C语言者指明一条可供参考的学习路径。 补充一&#xff1a;动态内存管理 核心函数&#xff08;需#include <stdlib.h>&#xff09;&#xf…

垃圾识别检测与分类数据集(猫脸码客第244期)

目标检测与垃圾&#xff1a;技术革新与环境管理的交汇点 在当今社会&#xff0c;城市化进程不断加速&#xff0c;人口持续增长&#xff0c;垃圾处理问题愈发凸显其重要性。有效管理垃圾&#xff0c;不仅关乎环境卫生状况&#xff0c;更直接影响到城市的可持续发展以及居民的生…

【调研报告】2025年与2030年AI及AI智能体 (Agent) 市场份额分析报告

2025年与2030年AI及AI智能体 (Agent) 市场份额分析报告 摘要 本报告旨在深入分析全球人工智能&#xff08;AI&#xff09;市场及其子领域AI智能体的未来发展轨迹&#xff0c;重点关注其在2025年和2030年的市场规模及其占全球GDP和整体AI市场的比例。分析表明&#xff0c;AI市…

台式机电脑CPU天梯图2025年6月份更新:CPU选购指南及推荐

组装电脑选硬件的过程中,CPU的选择无疑是最关键的,因为它是最核心的硬件,关乎着一台电脑的性能好坏。对于小白来说,CPU天梯图方便直接判断两款CPU性能高低,准确的说,是多核性能。下面给大家分享一下台式机电脑CPU天梯图2025年6月版,来看看吧。 桌面CPU性能排行榜2025 台…

小白学Pinia状态管理

目录 1. 什么是 Pinia&#xff1f; 2. 为什么需要 Pinia&#xff1f; 3. Pinia 的三个核心概念 State&#xff08;状态&#xff09;- 存储数据 Getters&#xff08;计算属性&#xff09;- 处理数据 Actions&#xff08;方法&#xff09;- 修改数据 4. 创建一个简单的 St…

Tauri2学习笔记

教程地址&#xff1a;https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引&#xff1a;https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多&#xff0c;我按照Tauri1的教程来学习&…

SQL进阶之旅 Day 26:分库分表环境中的SQL策略

【SQL进阶之旅 Day 26】分库分表环境中的SQL策略 文章简述 随着业务规模的扩大&#xff0c;单一数据库难以承载海量数据与高并发访问。分库分表成为解决这一问题的关键手段&#xff0c;但同时也带来了 SQL 查询复杂度的显著提升。本文作为“SQL进阶之旅”系列的第26天内容&…

linux之 内存管理(6)-arm64 内核虚拟地址空间变化

一、新内核变动 kernel变化的真快&#xff0c;之前我记得4.x的内核的内核空间的线性映射区位于内核空间的高地址处的128TB&#xff0c;且当前的博客和一些书籍也都还是这样介绍。可翻了翻kernel的Documentation/arm64/memory.rst文档&#xff0c;发现最新的kernel已将这128TB移…

循环神经网络(RNN):从理论到翻译

循环神经网络&#xff08;RNN&#xff09;是一种专为处理序列数据设计的神经网络&#xff0c;如时间序列、自然语言或语音。与传统的全连接神经网络不同&#xff0c;RNN具有"记忆"功能&#xff0c;通过循环传递信息&#xff0c;使其特别适合需要考虑上下文或顺序的任…

window批处理文件(.bat),用来清理git的master分支

echo off chcp 65001 > nul setlocal enabledelayedexpansionecho 正在检查Git仓库... git rev-parse --is-inside-work-tree >nul 2>&1 if %errorlevel% neq 0 (echo 错误&#xff1a;当前目录不是Git仓库&#xff01;pauseexit /b 1 )echo 警告&#xff1a;这将…

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…

【mysql】联合索引和单列索引的区别

区别核心&#xff1a;联合索引可加速多个字段组合查询&#xff0c;单列索引只能加速一个字段。 &#x1f539;联合索引&#xff08;复合索引&#xff09; INDEX(col1, col2, col3)适用范围&#xff1a; WHERE col1 ... ✅ WHERE col1 ... AND col2 ... ✅ WHERE col1 ..…