python迭代器与生成器详解


Posted in Python onMarch 10, 2016

例子

老规矩,先上一个代码:

def add(s, x):
 return s + x

def gen():
 for i in range(4):
  yield i

base = gen()
for n in [1, 10]:
 base = (add(i, n) for i in base)

print list(base)

这个东西输出可以脑补一下, 结果是[20,21,22,23], 而不是[10, 11, 12, 13]。 当时纠结了半天,一直没搞懂,后来齐老师稍微指点了一下, 突然想明白了--真够笨的,唉。。好了--正好趁机会稍微小结一下python里面的生成器。

迭代器(iterator)

要说生成器,必须首先说迭代器
区分iterable,iterator与itertion
讲到迭代器,就需要区别几个概念:iterable,iterator,itertion, 看着都差不多,其实不然。下面区分一下。

itertion: 就是迭代,一个接一个(one after another),是一个通用的概念,比如一个循环遍历某个数组。
iterable: 这个是可迭代对象,属于python的名词,范围也很广,可重复迭代,满足如下其中之一的都是iterable:
可以for循环: for i in iterable
可以按index索引的对象,也就是定义了__getitem__方法,比如list,str;
定义了__iter__方法。可以随意返回。
可以调用iter(obj)的对象,并且返回一个iterator
iterator: 迭代器对象,也属于python的名词,只能迭代一次。需要满足如下的迭代器协议
定义了__iter__方法,但是必须返回自身
定义了next方法,在python3.x是__next__。用来返回下一个值,并且当没有数据了,抛出StopIteration
可以保持当前的状态
首先str和list是iterable 但不是iterator:

In [3]: s = 'hi'

In [4]: s.__getitem__
Out[4]: <method-wrapper '__getitem__' of str object at 0x7f9457eed580>

In [5]: s.next # 没有next方法
---------------------------------------------------------------------------
AttributeError       Traceback (most recent call last)
<ipython-input-5-136d3c11be25> in <module>()
----> 1 s.next

AttributeError: 'str' object has no attribute 'next'

In [6]: l = [1,2] # 同理

In [7]: l.__iter__
Out[7]: <method-wrapper '__iter__' of list object at 0x7f945328c320>

In [8]: l.next
---------------------------------------------------------------------------
AttributeError       Traceback (most recent call last)
<ipython-input-8-c6f8fb94c4cd> in <module>()
----> 1 l.next

AttributeError: 'list' object has no attribute 'next'
In [9]: iter(s) is s #iter() 没有返回本身
Out[9]: False
In [10]: iter(l) is l #同理
Out[10]: False

但是对于iterator则不一样如下, 另外iterable可以支持多次迭代,而iterator在多次next之后,再次调用就会抛异常,只可以迭代一次。

In [13]: si = iter(s)

In [14]: si
Out[14]: <iterator at 0x7f9453279dd0>

In [15]: si.__iter__ # 有__iter__
Out[15]: <method-wrapper '__iter__' of iterator object at 0x7f9453279dd0>

In [16]: si.next #拥有next
Out[16]: <method-wrapper 'next' of iterator object at 0x7f9453279dd0>

In [20]: si.__iter__() is si #__iter__返回自己
Out[20]: True

这样,由这几个例子可以解释清楚这几个概念的区别。

自定义iterator 与数据分离

说到这里,迭代器对象基本出来了。下面大致说一下,如何让自定义的类的对象成为迭代器对象,其实就是定义__iter__和next方法:

In [1]: %paste
class DataIter(object):

 def __init__(self, *args):
  self.data = list(args)
  self.ind = 0

 def __iter__(self): #返回自身
  return self

 def next(self): # 返回数据
  if self.ind == len(self.data):
   raise StopIteration
  else:
   data = self.data[self.ind]
   self.ind += 1
   return data
## -- End pasted text --

In [9]: d = DataIter(1,2)

In [10]: for x in d: # 开始迭代
 ....:  print x
 ....:
1
2

In [13]: d.next() # 只能迭代一次,再次使用则会抛异常
---------------------------------------------------------------------------
StopIteration        Traceback (most recent call last)
----> 1 d.next()
<ipython-input-1-c44abc1904d8> in next(self)
  10  def next(self):
  11   if self.ind == len(self.data):
---> 12    raise StopIteration
  13   else:
  14    data = self.data[self.ind]

从next函数中只能向前取数据,一次取一个可以看出来,不过不能重复取数据,那这个可不可以解决呢?

我们知道iterator只能迭代一次,但是iterable对象则没有这个限制,因此我们可以把iterator从数据中分离出来,分别定义一个iterable与iterator如下:

class Data(object): # 只是iterable:可迭代对象而不iterator:迭代器

 def __init__(self, *args):
  self.data = list(args)

 def __iter__(self): # 并没有返回自身
  return DataIterator(self)


class DataIterator(object): # iterator: 迭代器

 def __init__(self, data):
  self.data = data.data
  self.ind = 0

 def __iter__(self):
  return self

 def next(self):
  if self.ind == len(self.data):
   raise StopIteration
  else:
   data = self.data[self.ind]
   self.ind += 1
   return data

if __name__ == '__main__':
 d = Data(1, 2, 3)
 for x in d:
  print x,
 for x in d:
  print x,

输出就是:

1,2,3
1,2,3
可以看出来数据可以复用,因为每次都返回一个DataIterator,但是数据却可以这样使用,这种实现方式很常见,比如xrange的实现便是这种数据与迭代分离的形式,但是很节省内存,如下:

In [8]: sys.getsizeof(range(1000000))
Out[8]: 8000072

In [9]: sys.getsizeof(xrange(1000000))
Out[9]: 40

另外有个小tips, 就是为什么可以使用for 迭代迭代器对象,原因就是for替我们做了next的活,以及接收StopIteration的处理。

迭代器大概就记录到这里了,下面开始一个特殊的更加优雅的迭代器: 生成器

生成器(generator)

首先需要明确的就是生成器也是iterator迭代器,因为它遵循了迭代器协议.

两种创建方式

包含yield的函数

生成器函数跟普通函数只有一点不一样,就是把 return 换成yield,其中yield是一个语法糖,内部实现了迭代器协议,同时保持状态可以挂起。如下:

def gen():
 print 'begin: generator'
 i = 0
 while True:
  print 'before return ', i
  yield i
  i += 1
  print 'after return ', i

a = gen()

In [10]: a #只是返回一个对象
Out[10]: <generator object gen at 0x7f40c33adfa0>

In [11]: a.next() #开始执行
begin: generator
before return 0
Out[11]: 0

In [12]: a.next()
after return 1
before return 1
Out[12]: 1

首先看到while True 不必惊慌,它只会一个一个的执行~
看结果可以看出一点东西:

调用gen()并没有真实执行函数,而是只是返回了一个生成器对象
执行第一次a.next()时,才真正执行函数,执行到yield一个返回值,然后就会挂起,保持当前的名字空间等状态。然后等待下一次的调用,从yield的下一行继续执行。
还有一种情况也会执行生成器函数,就是当检索生成器的元素时,如list(generator), 说白了就是当需要数据的时候,才会执行。

In [15]: def func():
 ....:  print 'begin'
 ....:  for i in range(4):
 ....:   yield i

In [16]: a = func()

In [17]: list(a) #检索数据,开始执行
begin
Out[17]: [0, 1, 2, 3]

yield还有其他高级应用,后面再慢慢学习。

生成器表达式

列表生成器十分方便:如下,求10以内的奇数:
[i  for i in range(10) if i % 2]

同样在python 2.4也引入了生成器表达式,而且形式非常类似,就是把[]换成了().

In [18]: a = ( i for i in range(4))

In [19]: a
Out[19]: <generator object <genexpr> at 0x7f40c2cfe410>

In [20]: a.next()
Out[20]: 0

可以看出生成器表达式创建了一个生成器,而且生有个特点就是惰性计算, 只有在被检索时候,才会被赋值。
之前有篇文章:python 默认参数问题及一个应用,最后有一个例子:

def multipliers():
 return (lambda x : i * x for i in range(4)) #修改成生成器
print [m(2) for m in multipliers()]

这个就是说,只有在执行m(2)的时候,生成器表达式里面的for才会开始从0循环,然后接着才是i * x,因此不存在那篇文章中的问题。

惰性计算这个特点很有用,上述就是一个应用,2gua这样说的:

性计算想像成水龙头,需要的时候打开,接完水了关掉,这时候数据流就暂停了,再需要的时候再打开水龙头,这时候数据仍是接着输出,不需要从头开始循环
其实本质跟迭代器差不多,不一次性把数据都那过来,需要的时候,才拿。

回到例子

看到这里,开始的例子应该大概可以有点清晰了,核心语句就是:

for n in [1, 10]:
 base = (add(i, n) for i in base)

在执行list(base)的时候,开始检索,然后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达式的过程。必须牢牢把握住这一点。

生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i, n)绑定的是n这个变量,而不是它当时的数值。

然后首先是第一次生成器表达式的执行过程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),这是第一次循环的结果(形象表示,其实已经计算出来了(10,11,12,3)),然后第二次,base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,终于得到结果了[20, 21, 22, 23].

具体执行过程可以在pythontutor上手动看看执行过程。

小结

概括
主要介绍了大概这样几点:

1.iterable,iterator与itertion的概念
2.迭代器协议
     自定义可迭代对象与迭代器分离,保证数据复用
3.生成器: 特殊的迭代器,内部实现了迭代器协议

其实这一块, 那几个概念搞清楚, ,这个很关键, 搞懂了后面就水到渠成了。而且对之前的知识也有很多加深。
比如常见list就是iterator与iteable分离实现的,本身是可迭代对象,但不是迭代器, 类似与xrange,但是又不同。
越来越明白,看源码的重要性了。 有地方写的不合适的, 请指正。

参考

http://www.shutupandship.com/2012/01/understanding-python-iterables-and.html
http://www.learningpython.com/2009/02/23/iterators-iterables-and-generators-oh-my/
http://stackoverflow.com/questions/9884132/what-exactly-are-pythons-iterator-iterable-and-iteration-protocols
http://python.jobbole.com/81881/

Python 相关文章推荐
Python使用urllib2模块实现断点续传下载的方法
Jun 17 Python
Python遍历文件夹和读写文件的实现代码
Aug 28 Python
Python3.6基于正则实现的计算器示例【无优化简单注释版】
Jun 14 Python
python将txt文件读入为np.array的方法
Oct 30 Python
Python中作用域的深入讲解
Dec 10 Python
Pytorch Tensor的索引与切片例子
Aug 18 Python
用Python爬取QQ音乐评论并制成词云图的实例
Aug 24 Python
python+rsync精确同步指定格式文件
Aug 29 Python
Python操作redis和mongoDB的方法
Dec 19 Python
Django-xadmin后台导入json数据及后台显示信息图标和主题更改方式
Mar 11 Python
详解Python流程控制语句
Oct 28 Python
Python+Tkinter打造签名设计工具
Apr 01 Python
Python装饰器基础详解
Mar 09 #Python
Python求算数平方根和约数的方法汇总
Mar 09 #Python
Python实现Linux命令xxd -i功能
Mar 06 #Python
基于Python实现一个简单的银行转账操作
Mar 06 #Python
Python切片知识解析
Mar 06 #Python
Django Admin实现上传图片校验功能
Mar 06 #Python
python如何通过protobuf实现rpc
Mar 06 #Python
You might like
Javascript与PHP验证用户输入URL地址是否正确
2014/10/09 PHP
php 生成签名及验证签名详解
2016/10/26 PHP
[转]JS宝典学习笔记
2007/02/07 Javascript
原生js和jquery实现图片轮播淡入淡出效果
2015/04/23 Javascript
在AngularJS应用中实现一些动画效果的代码
2015/06/18 Javascript
jquery性能优化高级技巧
2015/08/24 Javascript
javascript产生随机数方法汇总
2016/01/25 Javascript
jQuery监听文件上传实现进度条效果的方法
2016/10/16 Javascript
JS+CSS实现下拉刷新/上拉加载插件
2017/03/31 Javascript
javascript使用正则表达式实现注册登入校验
2020/09/23 Javascript
详解webpack的clean-webpack-plugin插件报错
2020/10/16 Javascript
angular8.5集成TinyMce5的使用和详细配置(推荐)
2020/11/16 Javascript
[07:09]2014DOTA2国际邀请赛-Newbee再次发威成功晋级决赛
2014/07/19 DOTA
在Python的gevent框架下执行异步的Solr查询的教程
2015/04/16 Python
python基础知识小结之集合
2015/11/25 Python
Python脚本实现自动发带图的微博
2016/04/27 Python
详解如何使用Python编写vim插件
2017/11/28 Python
在matplotlib的图中设置中文标签的方法
2018/12/13 Python
Python数据预处理之数据规范化(归一化)示例
2019/01/08 Python
python 实现turtle画图并导出图片格式的文件
2019/12/07 Python
Python 面向对象之类class和对象基本用法示例
2020/02/02 Python
简单了解django处理跨域请求最佳解决方案
2020/03/25 Python
基于python实现数组格式参数加密计算
2020/04/21 Python
Python中常用的os操作汇总
2020/11/05 Python
开普敦通行证:Cape Town Pass
2019/07/18 全球购物
企业治理工作自我评价
2013/09/26 职场文书
旷课检讨书3000字
2014/02/04 职场文书
售后服务经理岗位职责
2014/02/25 职场文书
员工生日活动方案
2014/08/24 职场文书
2014年办公室工作总结范文
2014/11/12 职场文书
2015年计生协会工作总结
2015/04/24 职场文书
小学六一主持词开场白
2015/05/28 职场文书
秀!学妹看见都惊呆的Python小招数!【详细语言特性使用技巧】
2021/04/27 Python
详解Spring Security中的HttpBasic登录验证模式
2022/03/17 Java/Android
Python实现仓库管理系统
2022/05/30 Python
Mysql中@和@@符号的详细使用指南
2022/06/05 MySQL