Python 生成器和协程

Python3 迭代器与生成器

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()

字符串列表,元组,集合、字典、range()、文件句柄可迭代对象(iterable)都可用于创建迭代器

  • 内部含有__iter__()方法的就是可迭代对象,遵循可迭代协议。
  • 可迭代对象.__iter__() 或者 iter(可迭代对象)化成迭代器

迭代器对象可以使用常规for语句进行遍历:

也可以使用 next() 函数:

创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() __next__()

如果你已经了解的面向对象编程,就知道类都有一个构造函数,Python 的构造函数为 __init__(), 它会在对象初始化的时候执行。

1
2
3
__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
 
__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。

创建一个返回数字的迭代器(计数器),初始值为 1,逐步递增 1:

StopIteration

  StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

那么如何判断一个对象是否是可迭代对象?

  1. 内部是否含有__iter__方法:
  2. 借助 collectionsIterable,Iterator 判断类型

生成器

  在 Python 中,使用了 yield 的函数被称为生成器generator)。

  跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器

  在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

  调用一个生成器函数,返回的是一个迭代器对象

  yield Vs return:

1
return返回后,函数状态终止,而yield会保存当前函数的执行状态,在返回后,函数又回到之前保存的状态继续执行。
    • return 终止函数,yield 不会终止生成器函数。
    • 都会返回一个值,return函数的执行者返回值,yield是给next()返回值

  以下实例使用 yield 实现斐波那契数列:

  send向生成器中发送数据。send的作用相当于next,只是在驱动生成器继续执行的同时还可以向生成器中传递数据。

yield from关键字

  yield from 将一个可迭代对象变成一个迭代器返回,也可以说,yield from关键字可以直接返回一个生成器

为什么使用生成器

更容易使用,代码量较小内存使用更加高效。比如:

  1. 列表是在建立的时候就分配所有的内存空间,
  2. 而生成器仅仅是需要的时候才使用,更像一个记录代表了一个无限的流。有点像数据库操作单条记录使用的游标
  • 如果我们要读取并使用的内容远远超过内存,但是需要对所有的流中的内容进行处理,那么生成器是一个很好的选择,
  • 比如可以让生成器返回当前的处理状态,由于它可以保存状态,那么下一次直接处理即可。

协程

  根据维基百科给出的定义,“协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”,那么你就想对了。

协程,又称微线程,纤程。英文名Coroutine

协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

1
2
3
4
# 与多线程、多进程等并发模型不同,协程依靠user-space调度,而线程、进程则是依靠kernel来进行调度。
# 线程、进程间切换都需要从用户态进入内核态,而协程的切换完全是在用户态完成,且不像线程进行抢占式调度,协程是非抢占式的调度。
# 通常多个运行在同一调度器中的协程运行在一个线程内,这也消除掉了多线程同步等带来的编程复杂性。同一时刻同一调度器中的协程只有一个会处于运行状态,这一点很容易从前言得出。
一个通常的误解是协程不能利用CPU的多核心,通过利用多个线程多个调度器,协程也是可以用到CPU多核心性能的。

 

1
2
3
4
5
6
7
8
协程的定义
 
协程最早的描述是由Melvin Conway于1958给出“subroutines who act as the master program”(与主程序行为类似的子例程),此后他又在博士论文中给出了如下定义:
 
· the values of data local to a coroutine persist between successive calls(协程的局部数据在后续调用中始终保持)
 
· the execution of a coroutine is suspended as control leaves it, only to carry on where it left off when control re-enters the coroutine at some later stage
(当控制流程离开时,协程的执行被挂起,此后控制流程再次进入这个协程时,这个协程只应从上次离开挂起的地方继续)。

  

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

  • 最大的优势就是协程极高的执行效率。
    • 因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,
    • 和多线程比,线程数量越多,协程的性能优势就越明显。
  • 第二大优势就是不需要多线程的锁机制
    • 因为只有一个线程,也不存在同时写变量冲突,
    • 在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?

  • 最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
  • Python对协程的支持是通过generator实现的。

使用yield实现协程

使用yield from实现的协程

  执行结果:

asyncio模块

  asyncioPython 3.4版本引入的标准库,直接内置了对异步IO的支持。

  用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

  asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

coroutine+yield from

 为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

 请注意,async和 await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

  1. @asyncio.coroutine替换为async
  2. yield from替换为await
async+await
  在协程函数中,可以通过await语法来挂起自身的协程,并等待另一个协程完成直到返回结果:

执行多个任务

 结果:

1
2
3
4
Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)

获取返回值

 结果:

执行多个任务获取返回值

结果:

  

posted @   Eagle_Fly  阅读(1006)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示