Python 协程学习有点难度?这篇文字值得你去收藏

news2024/12/22 20:58:59

Python 协程在基础学习阶段,属于有难度的知识点,建议大家在学习的时候,一定要反复练习。

Python 中的协程是一种用户态的轻量级线程。它与普通的线程不同,普通线程是由操作系统调度的,而协程是由程序自己调度的。因此,协程可以更高效地使用系统资源,更快地响应请求。

文章目录

    • Python 协程的简单实现
    • Python 协程高级用法
      • 定义协程返回值
      • 取消协程
      • 协程中的异常处理
      • 使用 asyncio.Queue 类来在协程之间传递消息
      • 使用 asyncio.Lock 类来保证协程之间的互斥访问
      • 使用 asyncio.Semaphore 类来限制协程的并发数量
    • Python 协程实现网络异步请求
    • Python 协程读写文件
    • Python 协程扩展

Python 协程的简单实现

在 Python 中,协程可以使用 async/await 关键字来实现。使用 async 关键字声明一个协程,在协程中使用 await 关键字来等待其他协程的完成。

例如,下面的代码定义了一个协程,在这个协程中会等待另一个协程的完成:

import asyncio


async def first_coroutine():
    print('协程 1')


async def second_coroutine():
    print('协程 2')
    await first_coroutine()
    print('协程 2 在执行协程 1 之后的输出')


async def main():
    await second_coroutine()


asyncio.run(main())

上述代码如果你在 Python3.7 之前的版本中运行,会出现错误 module 'asyncio' has no attribute 'run',如果你不想进行版本升级,可以使用 get_event_loop() 函数获取事件循环,然后通过 run_until_complete() 函数来运行协程,例如:

import asyncio


async def first_coroutine():
    print('协程 1')


async def second_coroutine():
    print('协程 2')
    await first_coroutine()
    print('协程 2 在执行协程 1 之后的输出')


async def main():
    await second_coroutine()


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

运行代码,得到下述内容输出。

在这里插入图片描述

另一个方法是使用 asyncio.get_event_loop().run_until_complete(main()),示例代码如下。

import asyncio


async def first_coroutine():
    print('协程 1')


async def second_coroutine():
    print('协程 2')
    await first_coroutine()
    print('协程 2 在执行协程 1 之后的输出')


async def main():
    await second_coroutine()


# loop = asyncio.get_event_loop()
# loop.run_until_complete(main())
# loop.close()

asyncio.get_event_loop().run_until_complete(main())

最后要注意,使用协程后, 不要忘记关闭事件循环,使用 loop.close() 来关闭事件循环。

Python 协程高级用法

定义协程返回值

协程可以使用 return 语句来返回值,但是在调用协程时需要使用 asyncio.create_task()asyncio.ensure_future() 函数来创建任务,然后使用 await 关键字来等待结果。

下面是一个定义协程返回值的示例:

import asyncio


async def my_coro():
    return "协程内部返回值,梦想橡皮擦"


async def main():
    result = await my_coro()
    print(result)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

在这个示例中,my_coro() 协程使用 return 语句来返回一个字符串。在 main() 协程中使用 await 关键字来等待 my_coro() 协程的完成,并将结果保存到 result 变量中。最后, 使用 print(result) 打印出结果。

main() 函数中使用 asyncio.create_task()asyncio.ensure_future() 来创建任务, 以等待协程的结果, 也是可行的。

示例如下,代码仅支持 Python3.7 以上版本。

import asyncio


async def my_coro():
    return "协程内部返回值,梦想橡皮擦"


async def main():
    task = asyncio.create_task(my_coro())
    result = await task
    print(result)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

如果你使用的是 Python3.7 及以下版本,需要使用 asyncio.ensure_future() 来创建协程任务。

import asyncio


async def my_coro():
    return "协程内部返回值,梦想橡皮擦"


async def main():
    task = asyncio.ensure_future(my_coro())
    result = await task
    print(result)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

取消协程

在 Python 中,可以使用 asyncio.CancelledError 异常来取消协程。

首先,使用 asyncio.create_task()asyncio.ensure_future() 创建协程任务。然后,使用 task.cancel() 方法来取消协程任务。

以下代码演示了如何在 10 秒后取消协程任务:

import asyncio


async def my_coro():
    try:
        while True:
            print("梦想橡皮擦,你好")
            await asyncio.sleep(1)
    except asyncio.CancelledError:
        print("取消协程任务")


async def main():
    task = asyncio.ensure_future(my_coro())
    await asyncio.sleep(10)
    task.cancel()


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

在这个示例中,my_coro() 协程在一个无限循环中打印 "梦想橡皮擦,你好",每次等待 1 秒。在 main() 协程中使用 asyncio.ensure_future() 创建该协程的任务,并在 10 秒后使用 task.cancel() 取消协程任务。

这里需要注意,如果协程没有捕获 CancelledError 异常, 它不会知道自己被取消了。还可以使用 loop.stop() 来停止事件循环,并取消所有正在运行的协程,在下一次调用 run_forever() 时,它会自动取消所有协程。

协程中的异常处理

在协程中可以使用 try/except 语句来捕获和处理异常。

import asyncio


async def my_coro():
    try:
        print("启动协程")
        await asyncio.sleep(1)  # 等待 1 秒
        1 / 0  # 编写一段错误代码
        print("协程结束")
    except ZeroDivisionError as e:
        print(f"异常: {e}")
    finally:
        print("橡皮擦的代码")


async def main():
    task = asyncio.ensure_future(my_coro())
    await task


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

在这个例子中,我们定义了一个名为 my_coro() 的协程。在协程中,使用 try/except 块来捕获 ZeroDivisionError 异常。当这个异常发生时,将打印 "异常:: division by zero"

在 try/except 块后面还有一个 finally 块。这个块中的代码总是会被执行,无论是否发生异常。在本例中,将打印 "橡皮擦的代码"

main() 函数中, 我们依旧使用 asyncio.ensure_future() 创建 my_coro() 的任务,并使用 await task 等待它完成。

使用 asyncio.Queue 类来在协程之间传递消息

asyncio.Queue 类是 Python 中异步编程中常用的队列类,可用于在协程之间传递消息。这个类可以在协程之间传递消息,可以使用 put() 函数来添加消息,使用 get() 函数来获取消息。

import asyncio


async def producer(queue: asyncio.Queue):
    for i in range(5):
        await queue.put(i)
        print(f"生产者 {i}")
        await asyncio.sleep(1)


async def consumer(queue: asyncio.Queue):
    while True:
        value = await queue.get()
        print(f"消费者 {value}")
        if value == 4:
            break


async def main():
    queue = asyncio.Queue()
    producer_task = asyncio.ensure_future(producer(queue))
    consumer_task = asyncio.ensure_future(consumer(queue))
    await producer_task
    await consumer_task


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

在这里插入图片描述
本案例中,我们定义了两个协程:producerconsumer。 producer 协程每秒生产一个整数,并使用 asyncio.Queue.put() 方法将其放入队列。 consumer 协程循环地从队列中获取消息,使用 asyncio.Queue.get() 方法取出队列中的值,并打印出来。如果消费到 4 就退出循环。

在 main() 函数中,我们创建了一个 asyncio.Queue 的实例,并分别创建了 producer 和 consumer 协程的任务。然后使用 await producer_taskawait consumer_task 等待两个任务完成。

使用 asyncio.Lock 类来保证协程之间的互斥访问

asyncio.Lock 类是 Python 中 asyncio 库中用来保证协程之间互斥访问的类,这个类可以保证在同一时刻只有一个协程在访问共享资源。

下面为搭建演示一下如何使用该类。

import asyncio


async def my_coro(lock: asyncio.Lock, i):
    async with lock:
        print(f"协程 {i} 已经获得锁")
        await asyncio.sleep(1)
        print(f"协程 {i} 已经释放锁")


async def main():
    lock = asyncio.Lock()
    task1 = asyncio.ensure_future(my_coro(lock, 1))
    task2 = asyncio.ensure_future(my_coro(lock, 2))
    await task1
    await task2


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

在这里插入图片描述
本案例中 ,我们定义了一个名为 my_coro() 的协程。它接受一个 asyncio.Lock 实例和一个整数作为参数。在协程中,我们使用 async with lock: 块来请求获取锁。如果锁当前未被占用,则将获取锁,并执行块中的代码。如果锁已被占用,则协程将被阻塞,直到锁被释放。在这里,当一个协程获取了锁之后,它会打印 “协程 {i} 已经获得锁”,然后睡眠 1s, 打印 “协程 {i} 已经释放锁”。

因为 Lock 是互斥锁,所以两个协程获取锁的时间是互不干扰的,并且不会发生竞争情况。

使用 asyncio.Semaphore 类来限制协程的并发数量

asyncio.Semaphore 类是 Python 中 asyncio 库中用来限制协程并发数量的类。

import asyncio


async def my_coro(semaphore: asyncio.Semaphore, i):
    async with semaphore:
        print(f"协程 {i} 正在运行")
        await asyncio.sleep(1)
        print(f"协程 {i} 结束")


async def main():
    semaphore = asyncio.Semaphore(3)
    tasks = []
    for i in range(10):
        tasks.append(asyncio.ensure_future(my_coro(semaphore, i)))
    await asyncio.gather(*tasks)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

上述代码运行效果如图。
在这里插入图片描述
在本案例中,我们定义了一个名为 my_coro() 的协程。它接受一个 asyncio.Semaphore 实例和一个整数作为参数。在协程中,我们使用 async with semaphore: 块来请求获取信号量。如果信号量的计数值大于 0,则将获取信号量,计数值减 1,并执行块中的代码。如果计数值等于 0,则协程将被阻塞,直到信号量被释放。

在 main() 函数中,我们创建了一个 asyncio.Semaphore 的实例,并创建了 10 个 my_coro() 协程的任务。因为我们限制信号量只有 3 个许可,所以同时只有 3 个协程可以运行。最后通过 await asyncio.gather(*tasks) 来等待任务结束。

asyncio.gather(*tasks) 是 asyncio 库中用于并行运行多个协程的函数。它接受多个协程的任务作为参数,并返回一个新的协程,这个新协程会在所有传入的协程任务都完成后结束。

在前文代码中, 我们创建了 10 个 my_coro() 协程的任务,并使用 asyncio.gather(*tasks) 来等待这些任务的完成。这样做的效果相当于等待所有任务结束并返回结果,而不是串行等待每个任务结束。

Python 协程实现网络异步请求

使用 async/await 关键字来实现异步网络请求。

import aiohttp
import asyncio


async 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://bing.com')
        print(html)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

上述代码用到 aiohttp 库,需要提前进行安装。

这个程序使用 aiohttp 库来发送网络请求,fetch() 函数是一个协程,它使用 await关键字来等待响应。main() 函数也是一个协程,它使用 await 关键字来等待 fetch() 函数的完成。

在此基础上,我们扩展到多个网络请求,代码如下。

import aiohttp
import asyncio


async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()


async def main():
    async with aiohttp.ClientSession() as session:
        html1, html2 = await asyncio.gather(
            fetch(session, 'https://bing.com'),
            fetch(session, 'https://baidu.com')
        )
        print(html1)
        print(html2)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

这个程序使用 asyncio.gather() 函数来并发地运行两个 fetch() 函数。

Python 协程读写文件

示例代码如下,请提前在 Python 文件目录准备 ca.txt 文件。

import asyncio


async def read_file(file_name):
    with open(file_name, 'r',encoding='utf-8') as f:
        return f.read()


async def write_file(file_name, data):
    with open(file_name, 'w',encoding='utf-8') as f:
        f.write(data)


async def main():
    data = await read_file('ca.txt')
    await write_file('ca_copy.txt', data)

asyncio.get_event_loop().run_until_complete(main())

该程序使用read_file()write_file() 两个协程来读取和写入文件。在 main() 函数中使用 await 关键字来等待这两个协程的完成。

使用 asyncio.get_event_loop().run_until_complete(main()) 来运行协程时,是不需要手动关闭协程的,该方法会在协程完成后自动关闭事件循环。这个方法会阻塞程序直到协程完成,所以在协程运行结束后就可以直接结束程序了。

与之对应的高版本 Python 使用的 asyncio.run(main()),这个函数会自动创建一个事件循环并在协程完成后关闭它,也不需要再调用 close()方法关闭事件循环。

Python 协程扩展

协程是一种非常强大的编程模型,它可以帮助我们更好地处理并发和异步编程。除了我之前提到的内容之外,还有一些其他的知识点可以补充:

  • 使用 asyncio.shield() 函数来保护协程不被取消。
  • 使用 asyncio.as_completed() 函数来迭代完成的协程的结果。
  • 使用 asyncio.wait() 函数来等待多个协程完成,并返回已完成的协程的结果。

asyncio 库中还有很多其他类和函数可以帮助我们处理多种不同类型的并发和异步编程场景,如:asyncio.Conditionasyncio.Eventasyncio.Barrierasyncio.TimeoutError 等。

协程与线程的区别

协程是一种用户态的轻量级线程,它可以在一个线程中被调度和执行,而线程是操作系统级别的线程,通常需要更多的系统资源来进行调度和执行。由于协程可以在一个线程中被调度和执行,所以它可以充分利用线程的资源,并可以在不同的协程之间切换,实现高效的并发。而线程则需要在不同的线程之间切换,并且由于需要更多的系统资源进行调度和执行,所以并发性能通常不如协程。

📢📢📢📢📢📢
💗 你正在阅读 【梦想橡皮擦】 的博客
👍 阅读完毕,可以点点小手赞一下
🌻 发现错误,直接评论区中指正吧
📆 橡皮擦的第 831 篇原创博客

从订购之日起,案例 5 年内保证更新

  • ⭐️ Python 爬虫 120,点击订购 ⭐️
  • ⭐️ 爬虫 100 例教程,点击订购 ⭐️

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

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

相关文章

【ESP 保姆级教程】玩转emqx篇③ ——认证安全之使用内置数据库(Mnesia)的密码认证

忘记过去,超越自己 ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-01-15 ❤️❤️ 本篇更新记录 2022-01-15 ❤️🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝&#x1f64…

Transformer模型详解相关了解

文章目录Transformer模型详解1.前言1.1 Transformer 整体结构1.2 Transformer 的工作流程2. Transformer 的输入2.1 单词 Embedding2.2 位置 Embedding3. Self-Attention(自注意力机制)3.1 Self-Attention 结构3.2 Q, K, V 的计算3.3 Self-Attention 的输…

《神经网络与深度学习》 邱希鹏 学习笔记(一)

一、机器学习的基本要素 机器学习的基本要素: 模型 学习准则 优化算法 其中模型分为线性和非线性。学习准则有用损失函数来评价模型的好坏,还有经验风险最小化准则,大概意思就是在平均损失函数中获得最小的损失函数,但是因为样本可能很小&…

Goodbye 2022,Welcome 2023 | 锁定 2023

引言又是一年春来到,新年应比旧年好。旧岁已辞,新年已到,新旧更迭之际,真想剪个头发换身行头,就能重新出发。但终究是要回头看看啊,那一路而来的荆棘与芬芳,才是成长的印记啊。那就回拨记忆&…

和涤生大数据的故事

1自我介绍 大家好,我是泰罗奥特曼,毕业于东北的一所不知名一本大学,学校在一个小城市里面,最热闹的地方是一个四层楼的商城,专业是信息管理与信息系统,由于是调剂的,所以我也不知道这个专业是干…

一篇文章带你学完JavaScript基础知识,超全的JavaScript知识点总结

目录 内置函数 alert警告框 promopt提示框 console控制台 字面量 数字型 字符串型 变量 声明与赋值 类型检测 类型转换 比较运算符 逻辑运算符 条件句 if else switch break,continue while 赋值运算符 函数 关键字形式函数 变量认知 作用域 表达式…

什么样的故障让阿里云换了总裁?

📣📣📣📣📣📣📣 🎍大家好,我是慕枫 🎍前阿里巴巴高级工程师,InfoQ签约作者、阿里云专家博主,一直致力于用大白话讲解技术知识 &#x…

SpringBoot数据访问Redis

目录 前言 1、Redis自动配置 2、RedisTemplate与Lettuce 3、切换至jedis 前言 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串…

基于贝叶斯算法的邮件过滤管理系统的设计和实现(Vue+SpringBoot)

作者主页:Designer 小郑 作者简介:Java全栈软件工程师一枚,来自浙江宁波,负责开发管理公司OA项目,专注软件前后端开发(Vue、SpringBoot和微信小程序)、系统定制、远程技术指导。CSDN学院、蓝桥云…

Java对象引用级别

为了使程序能更灵活地控制对象生命周期,从 JDK1.2 版本开始,JDK把对象的引用级别由高到低分为强引用、软引用、弱引用、虚引用四种级别。 强引用 StrongReference 强引用是我们最常见的对象,它属于不可回收资源,垃圾回收器&…

区块链技术3--BTC协议

1数字货币中经常出现的问题:双花攻击 数字货币本身为带有签名的数据文件,可以进行复制。即:对用户来说,可以将同一货币花费两次。对货币添加唯一编号(不可篡改),每次支付向货币发行单位查询真伪…

数据标注平台(CVAT)安装及踩坑记录

目录 一、CVAT安装 step1 安装docker step2 获取权限 step3 获取权限 step4 克隆cvat源代码 step5 构建docker镜像 step6 运行Docker容器这一步要下载公共docker映像,耗时看网速,但是不会太久。 step6 创建管理员用户 step7 关闭cvat服务 二、…

算法第十二期——BFS-判重

目录 BFS判重 Python判重方法: set、字典 set()判重 字典判重 例题:跳蚱蜢 思路 【建模】 去重 代码一:字典去重(用list实现队列) 代码二:set()去重(用list实现队列) 代码二&#xff…

CRMEB开源商城部署在腾讯云2

目录PHP在安装过程中会监测Redish5跨域PHP在安装过程中会监测Redis public\install\index.php if (extension_loaded(redis)) {$redis <span class"correct_span">&radic;</span> 已安装;} else {$redis <a href"https://doc.crmeb.com/w…

2个大厂 100亿级 超大流量 红包 架构方案

2个大厂 100亿级 超大流量 红包 架构方案 文章目录2个大厂 100亿级 超大流量 红包 架构方案100亿级 红包 应用 场景概述百亿级 微信红包技术架构架构**南北分布****拆红包入账异步化****发拆落地&#xff0c;其他操作双层cache**高并发**红包算法****柔性降级方案**360w QPS 10…

Nginx与LUA(3)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;在互联网应用中&#xff0c;很多场景都会涉及到高并发请求&#xff0c;如果不对这些请求做限制&#xff0c;那么服务器很快就会被挤垮。就像在12306买票一样&…

计算机图形学实习教程之基本图形的生成(扫描线填充算法+图形缩放算法+对称变换算法+消隐算法+金刚石图案算法),利用C#实现,附源码

环境&#xff1a;Win10Visual Studio 2022 Community 在本次实验中需要用到第一篇文章实验内容的代码及环境&#xff0c;详情请见&#xff1a;传送门 目录 一、实验目的 二、实验步骤 1.扫描线填充算法 2.图形的缩放算法 3.对称变换算法 4.消隐算法 5.金刚石图形算法 一…

Unity 3D 人形角色动画(Avatar)||Unity 3D 导航系统||Unity 3D 障碍物

Unity 3D 人形角色动画&#xff08;Avatar&#xff09; Mecanim 动画系统适合人形角色动画的制作&#xff0c;人形骨架是在游戏中普遍采用的一种骨架结构。。 由于人形骨架在骨骼结构上的相似性&#xff0c;用户可以将动画效果从一个人形骨架映射到另一个人形骨架&#xff0c…

基于Java+SpringBoot+Vue求职招聘系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供毕业项目实战✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《Spring家族及微服务系列》专注…

《Buildozer打包实战指南》第一节 在虚拟机中安装Ubuntu系统

目录 1.1 下载并安装Virtual Box虚拟机 1.2 下载并安装Ubuntu系统 由于Buildozer不能在Windows系统上打包&#xff0c;只能运行于Linux&#xff0c;所以我们可以在Windows系统上安装一个虚拟机&#xff0c;并在虚拟机中安装Linux。在本教程中笔者将会一直使用Ubuntu系统&…