古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。

Python高级编程和异步I/O并发编程笔记 8 迭代器和生成器

Python admin 347℃ 0评论

迭代器和生成器是python异步IO(协程)并发编程的基础,尤其重要的是生成器。生成器在大部分场景中是用来产出数据的,即yield,但其实还有两个重要的应用,分别是“yield from”和send()函数,二者在协程中应用较多。

python的迭代协议

迭代器是访问集合内元素的一种方式,一般用来遍历数据。在编程规范中,迭代器模式是经典设计模式的一种。不同于以下标的方式访问(原理如之前所提到的__getitem__魔法函数),迭代器是不能返回的,只能一条一条的产生数据,提供了一种“惰性”访问数据的方式。for循环的基础就是迭代器,能够做for循环操作的类型都是实现了迭代协议的。迭代协议就是“__iter__”魔法方法,实现了__iter__方法就是一个可迭代类型,可迭代类型和迭代器不是一个概念。

迭代器和可迭代对象

python中有内置函数“iter()”,可以接收一个可迭代对象,返回一个迭代器,如下例所示:

python对对象进行for循环,其内部实现逻辑也是类似上面的原理,会调用iter()内置函数,尝试查找对象的__iter__()方法,如果查找失败则会“退化”查找__getitem__()。

生成器函数的使用

掌握生成器是理解协程的基础。生成器函数就是包含有“yield”关键字的函数,如下例。

不同于return语句在函数只要return一般是无法继续return的,生成器函数返回的是一个生成器对象,是一个迭代器,所以yield后续还可以继续yiled。

这是python语法中非常精妙的设计,首先利用yield关键字或者说生成器实现协程成为可能,然后也为惰性求值(延迟求值)提供了可能。

生成器的原理

python函数的运行原理是:python解释器(python.exe,是使用C语言实现的)会调用“PyEval_EvalFramEx(C语言实现的函数)”方法去执行脚本中函数,在运行脚本中函数之前,会首先创建一个栈帧(stack frame,或者成为堆栈)保存上下。python中一切皆对象,栈帧也是一个对象,该栈帧对象会将脚本中函数变成一个字节码对象,如下例,我们可以使用dis模块查看函数的字节码。

继续上文,在运行脚本内函数,如foo函数之前,首先会创建栈帧对象,在栈帧对象的上下文中去运行字节码,函数的字节码是全局唯一的,因为函数是全局唯一的。而在foo中调用子函数bar函数时,又会创建一个栈帧,然后将PyEval_EvalFramEx函数的控制权交给这个新建栈帧,在新建栈帧的上下文中运行bar的字节码,机制类似于“递归”。需要注意的是,所有栈帧都是分配在堆内存上,堆内存的特性就是不去释放就会一直存在在内存中,这就决定了栈帧可以独立于调用者存在。

python的函数调用过程

生成器对象的原理就是应用了python函数调用的过程,最关键的是利用了“所有的栈帧都是分配在堆内存上的”,这样生成器才有了实现的可能。生成器对象实际上是对PyFrameObject对象进行了封装,其结构如下图:

生成器的结构

如上,其中“f_lasti”会指向最近执行的字节码位置,“f_locals”会存在变量字典。

利用上面这种机制,python利用生成器对函数的暂停(通过yield)和继续前进就有了理论基础,因为每次操作都会记录执行位置和localos变量,所以可以实现对函数整个运行过程的控制。而生成器对象也是分配在堆内存中的,所以可以独立于调用存在。由于gen_func()每次调用都会重新生成一个栈帧对象,所以只要有这个生成器对象我们就可以控制函数的运行,所以在任何函数、任何模块中只要拿到这个生成器对象,我们都可以恢复、暂停其运行,正因为我们可以在任何地方控制它,才有了“协程”概念的理论基础。

  • 通过UserList来学习生成器的应用

如上文所述,在对可迭代对象(如list)进行for遍历时,python是调用了内置的“iter()”方法,首先查找对象的__iter__方法是否实现,如果没有则退化到查找__getitem__方法。其实现逻辑我们可以从源码中查看,python的list是使用C语言实现的,无法查看源码,python提供了一个由python语言实现的list,即UserList,其用python解释了list是如何实现的(用python实现了一遍),另外也可以被用户继承,因为有些场景我们需要继承list(不要继承全局的list,因为其使用C语言实现,内部有很多优化,很多关键的方法不允许覆盖)。

  • 利用生成器读取大文件

在python中使用open()、readlines()读取超大文件(如500G)时一次性加载是不可行的,

转载请注明:北凉柿子 » Python高级编程和异步I/O并发编程笔记 8 迭代器和生成器

喜欢 (1)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址