Python 中的 iterable, iterator 以及 generator,一直是非常亲密但是难以区分的概念。nvie 有一个很好的 帖子阐述了它们之间的关系,但是内容偏向于概括和总结,对于新手来说仍然难以理解。Fluent Python 的第 14 章也有非常好的演绎,但是我认为它对「为什么要有这种语言特性」缺乏阐释。我试图从演变的角度,总结这些概念的来源和演化,以得到一个符合逻辑和容易理解的版本。

Simple Loop

几乎每一个 Python 入门教程,都会用类似下面的代码来讲述最简单的 for 循环:

>>> l = [2, 1, 3]

>>> for i in l:

... print(i)

2

1

3

在 Python 中,执行 l[i] 实际上是调用了 l 的 __getitem__ 函数。list 类型中会实现了这个函数,用来返回某个 index 下的元素。而早期 Python 的实现上利用了这个操作。上面的 for 循环实际上是从 l[0] 开始取元素,等价于这段代码:

i = 0

while True:

try:

print(l[i]) # 亦可是 print(l.__getitem__(i))

i += 1

except IndexError:

break

Python 内置的大多数容器类型(list, tuple, set, dict)都支持 __getitem__,因此它们都可以用在 for .. in循环中。如果你有个自定义类型也想用在循环中,你需要在类里面实现一个 __getitem__ 函数,Python 就会识别到并使用它。Fluent Python 一书提供了一个例子。

Lazy Evaluation

在上面的代码例子中,l 的值是全部被加载到内存中,再在循环中被一个一个取出来的。设想这样一个场景,你要从数据库中查询出一千万条数据做处理,

如果全部加载到内存,可能会将内存撑满

在处理第一条数据前,需要等待大量时间从数据库中取出这些数据

一些特殊的场景下,你可能并不需要对全部的数据做处理,比如处理到第五百万条数据时即可以结束

前辈们提出了惰性求值( Lazy Evaluation )来解决这个问题。有些地方也叫它「延迟加载」「懒加载」等。它的基本理念是「按需加载」,在上面的例子中,可以将取数据过程变成一页页取,比如先取 100 条数据进行处理,处理完后再取下一个 100 条,直至全部取完。

The Iterator Protocol

Python 为了在语言层面支持 lazy evaluation,给出了 iterator 协议。如果一个类实现了 __next__ 函数,并且:

每次调用该函数,都可以返回一个新的数据

没有新的数据时,调用它抛出 StopIteration 异常(当然如果序列是无限长,那么可以不抛)

那么这个类即支持 iterator 协议。于是「按需加载」,即可以通过每次 __next__ 被调用时去实现。Python 的内置函数 next(iterator) 实际上是调用 iterator.__next__。下面的例子给出一个 iterator 的实现,用来按需地计算出下一个斐波那契数:

>>> class FibonacciIterator:

... def __init__(self, maximum):

... # 为了简单,将初始值设为 1, 2 而不是 0, 1

... self.a, self.b = 1, 2

... self.maximum = maximum

... def __next__(self):

... fib = self.a

... if fib > self.maximum:

... raise StopIteration

... self.a, self.b = self.b, self.a + self.b

... return fib

>>> f = FibonacciIterator(5)

>>> next(f)

1

>>> next(f)

2

>>> next(f)

3

>>> next(f)

5

>>> next(f)

# StopIteration occured

Enhanced iterable

上文中的 FibonacciIterator 已经实现了按需加载,那可以直接将它用在 for 循环中吗?试试:

>>> for i in FibonacciIterator(5):

... print(i)

...

Traceback (most recent call last):

File "", line 1, in

TypeError: 'FibonacciIterator' object is not iterable

可以看到有 is not iterable 的报错。按上一节的描述,早期的 Python 仅在一个类有 __getitem__ 函数时 Python 才将它当成 iterable,同时为了配合新的 iterator 的机制,Python 在 2.2 版本中将 __iter__ 协议加入了进来:

一个类如果实现了 __iter__ 并返回一个 iterator,那么它是 iterable 的

如果没有实现 __iter__,但是有 __getitem__,那么它仍然是 iterable 的

那么对于一个 iterator,如果你想能在 for 循环中使用,只需要实现一个 __iter__ 函数返回自己就可以了:

>>> class FibonacciIterator:

... # **函数实现省略,见上文

... def __iter__(self):

... return self

>>> for i in FibonacciIterator(5):

... print(i)

1

2

3

5

Generator Function

上面虽然花了挺大篇幅讲述 iterator 的机制,但是事实上 Python 中以 __next__ 方式来实现 iterator 的并不多。Python 在 2.2 版本中支持了 iterator,但是也同时给出了另外一种更灵活方便,也更重要的机制 —— generator。

识别 generator 的标志在 yield 关键字。上文的斐波那契数列,用 generator 来实现是:

>>> def fib(maximum):

... a, b = 1, 2

... while a <= maximum:

... yield a

... a, b = b, a+b

...

>>> for i in fib(5):

... print(i)

1

2

3

5

上面的 fib() 虽然也是用 def 定义,但是它的函数体中有 yield 关键字,因此它不是个普通函数,而是个 generator function。它返回的是一个 generator object,即 fib(5) 处。generator 是一种在语言层面被支持的 iterator,它的规则是:

next() 调用一个 generator 时,Python 会执行函数体到下一个 yield 语句,并将 yield 后的值作用 next() 的返回值;然后该函数的执行暂停,直至下一次 next() 调用时,继续执行到下一个 yield

当整个函数体被执行完毕时,抛出 StopIteration 异常

这套规则清晰直观,可以将它套用在上面代码中验证一下。yield 及 generator 是非常重要的机制,不仅仅在于它比 iterator 更简单直观,而在于它同时引入了一种控制语言运行的机制。对于普通函数,一旦执行则必须从函数头执行到函数尾,之后才把控制权交给调用方;但是对于 generator function,你可以只执行一小段代码,即把控制权交回调用方(yield 时)。这种机制也对后面提出 coroutine 及 asyncio 中起到了重要的作用。

Generator Expression

试试运行下面的代码:

>>> sum([i**2 for i in range(11)])

385

>>> sum((i**2 for i in range(11)))

385

>>> sum(i**2 for i in range(11))

385

后两种写法,跟第一种有什么区别呢?后两种即是 generator expression,是一种方便生成 generator 的语法糖,形式上是用括号包裹的 list comprehension。背后的理念大概是这样:list comprehension 是用来生成元素的,generator 也是用来生成元素的,那为什么不提供一种类似 list comprehension 语法的 expression 来表示 generator 呢?它跟下面的代码是等价的:

def gen():

for i in range(11):

yield i**2

sum(gen())

至于第三种写法为啥不用括号包起来,是 Python 为了可读性故意设计的,如果作为唯一的函数参数使用,则可以省略。

总结

定义上:

实现了 __iter__ 或 __getitem__,并满足一定规则的类型是 iterable 的,它的实例是个 iterable

实现了 __next__ 并满足一定规则的类型,它是一种 iterator,它的实例是个 iterator

在函数体中使用了 yield 的函数是 generator function ;使用了括号包裹的类 list comprehension 是 generator expression。它们都会产生 generator

语言机制上:

for .. in 循环所消费的对象,需要是个 iterable

它们之间的关联:

容器类型(list, dict, etc.)大部分是 iterable ;dict 有多个不同函数生成不同用途的 iterator

iterator 大部分情况下是个 iterable

generator 是个 iterator,同时是个 iterable

文:giy.hkv

更多人工智能相关文章:http://siligence.ai

python iterable对象_一篇文章看懂 Python iterable,相关推荐

  1. c++ socket线程池原理_一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题...

    编辑:业余草来源:https://www.xttblog.com/?p=4946 一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题. ThreadLocal 相信不 ...

  2. python 结构体数组 定义_一篇文章弄懂Python中所有数组数据类型

    前言 数组类型是各种编程语言中基本的数组结构了,本文来盘点下Python中各种"数组"类型的实现. list tuple array.array str bytes bytearr ...

  3. python 推迟运行_一文看懂Python的time模块sleep()方法和strftime()方法

    概述 今天主要介绍一下Python的time sleep()方法和strftime()方法. 一.Python time sleep()方法 Python time sleep() 函数推迟调用线程的 ...

  4. bytes数组转string指定编码_一篇文章弄懂Python中所有数组数据类型

    前言 数组类型是各种编程语言中基本的数组结构了,本文来盘点下Python中各种"数组"类型的实现. list tuple array.array str bytes bytearr ...

  5. c 多线程运行混乱_一篇文章读懂 Python 多线程

    本文作者为 Michael Driscoll,是其新书 Python 201 的一节.本文译者为 linkcheng,由EarlGrey@编程派校对. 译者简介:linkcheng,专业电子信息工程. ...

  6. python装饰器函数执行后日志_一篇文章搞懂Python装饰器所有用法

    如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖. 它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上.和这个函数绑定在一起 ...

  7. python 闭包_一篇文章读懂Python的闭包与装饰器!

    什么是装饰器? 装饰器(Decorator)相对简单,咱们先介绍它:"装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数", ...

  8. 25q64存储多个数据_一篇文章看懂,存储虚拟化在不同用例中的实践与优势

    存储虚拟化是一种对物理存储资源进行抽象的技术,使其看起来像是一个集中的资源.虚拟化掩盖了管理内存.网络.服务器和存储中资源的复杂性. 存储虚拟化运行在多个存储设备上,使它们看起来就像一个单一的存储池. ...

  9. vue获取div中的值_一篇文章看懂Vue.js的11种传值通信方式

    面试的时候,也算是常考的一道题目了,而且,在日常的开发中,对于组件的封装,尤其是在 ui组件库中,会用到很多,下面,就来详细的了解下,通过这篇文章的学习,可以提升项目中组件封装的灵活性,可维护性,话不 ...

最新文章

  1. mysql 提交_MySQL 事务提交过程
  2. 求解第K个斐波那契质数
  3. SpringMVC之@requestBody的作用
  4. 魔兽世界工程学技能1-375冲级攻略
  5. 十分钟理解线性代数的本质_数学对于编程来说到底有多重要?来看看编程大佬眼里的线性代数!...
  6. 为什么将表格的method改为post后就无法工作_用Python将Keras深度学习模型部署为Web应用程序...
  7. 词法分析器c语言带注释,C语言词法分析器内容说明注释完整可运行代码.doc-资源下载在线文库www.lddoc.cn...
  8. VirtualBox设置共享目录(主机win7,虚拟机Ubuntu)
  9. 使用Mockito对类成员变量进行Mock
  10. Custom Components 翻译
  11. 华为向emui输入鸿蒙,新颜值/新功能/新体验!EMUI 11上手:手机鸿蒙OS的提前预演...
  12. SpringBoot-文件在线预览解决方案-基于OpenOffice及jacob
  13. [洛谷P1501][国家集训队]Tree II(LCT)
  14. 成为java高手的八大条件
  15. 关于AE中出现 “对 COM 组件的调用返回了错误 HRESULT E_FAIL” 错误
  16. 【Apple苹果设备刷机】ipad已停用,iTunes无法联系网络等问题
  17. Web前端第三季(JavaScript):十一:第3章: 字符串和对象:309-如何创建对象+310-如何创建构造函数+311-给对象添加普通函数和对象属性的遍历
  18. java 列表伸缩,微服务实例自动弹性伸缩实践
  19. 玩转这些视频制作软件,让你成为短视频高手
  20. (七)《数电》——CMOS与TTL门电路

热门文章

  1. 《CCNA安全640-554认证考试指南》——第6章在Cisco IOS设备上保护管理层
  2. 从前台获取的数据出现乱码的解决方法
  3. CSU 1329: 一行盒子
  4. java 方法的返回值(翻译自Java Tutorials)
  5. [CTO札记]给新助理的3句话
  6. 1、基于Feign的接口调用概述
  7. C\C++获取当前路径
  8. eigrp配置实验_EIGRP的认证的配置
  9. toastr-min.css,Toastr插件提示框使用说明
  10. Win-MASM64汇编语言-$/取当前行代码的地址