理解Python 生成器与迭代器

前言

前一段时间和同事聊到Python技术知识,发现自己对生成器,迭代器傻傻分不清楚,于是乎查文档,找资料,有了此文。

通过本文大家可以了解到迭代器,生成器,生成器表达式,容器的定义以及关系。

一图胜千言

关系图(http://nvie.com/posts/iterators-vs-generators/)

先对上面的关系进行解释说明

生成器包括生成器表达(generator expression)和生成器函数(generator function)。 生成器(generator)是迭代器(iterator),但是反过来不一定成立,同时生成器也是可迭代的。 迭代器(iterator)都是可迭代的(iterable),并且实现了next()/__next()__方法。 元组,列表,集合构成容器这些对象都是可迭代的。

接下来我们深入浅出的去了解迭代器,生成器是什么,如何使用。

可迭代对象

相信大家都知道迭代的含义,就是可以循环遍历。 那什么是可迭代对象?通俗的将就是可以使用for x in iterable_obj 或者while 循环遍历的对象,比如list,set,tuple,dict等对象。我们可以通过isinstance()方法来判断,参考例子:

In [30]: from collections import Iterable
In [31]: isinstance('python', Iterable)
Out[31]: True
In [32]: s=3           # s为数字,不可迭代
In [33]: isinstance(s, Iterable)
Out[33]: False
In [35]: l = [1,2,3,4] #列表可用for循环遍历,可迭代
In [35]: isinstance(l, Iterable)
Out[35]: True
In [37]: w=(1,2,3)     #元组可用for循环遍历,可迭代
In [38]: isinstance(w, Iterable)
Out[38]: True

从上面的检测来看 s=3 ,s是一个数值,不可迭代。其他的对象都是可以被循环访问的,即可迭代。

迭代器

迭代器是可以被next()函数调用并返回下一个值的对象,即Iterator。一个对象可迭代,是否就说明它是迭代器呢?我们继续使用isinstance 检测

In [37]: s=[1,2,3]      #列表可以用for循环遍历,可迭代
In [39]: from collections import Iterable,Iterator
In [40]: isinstance(s, Iterator)
Out[40]: False ##

从结果来看s是一个列表,可迭代的对象(iterable)不一定是迭代器(iterator)。为什么列表,集合,字典等对象是可迭代但是不是迭代器呢?

Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。 我们可以把iterator当做有序序列,但我们不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,只有在需要返回下一个数据时它才会计算,即(lazy evaluation)。构造有几百万个值的列表所占用的内存大于几十M,而迭代器对象占用几十个字节的空间。

如何构造迭代器呢?本文介绍两种方式:

  1. 为容器对象添加 __iter__() 和 __next__() 方法(Python 2.7 中是 next());__iter__() 返回迭代器对象本身 self,__next__() 则返回每次调用 next() 或迭代时的元素; class MyIterator(object): def __init__(self, max): self.max = max # 上边界 self.now = 0 # 当前迭代值,初始为0 def __iter__(self): return self # 返回迭代器本身 def next(self): # 迭代器类必须实现的方法next() while self.now < self.max: self.now += 1 return self.now - 1 #返回当前迭代值 raise StopIteration #超出上边界,抛出异常 In [51]: my=MyIterator(10) In [52]: isinstance(my,Iterable) Out[52]: True In [53]: isinstance(my,Iterator) Out[53]: True
  2. python内置函数 iter() 将可迭代对象转化为迭代器。 l 是一个列表,通过iter() 函数将列表对象转化为迭代器。 In [45]: isinstance(l, Iterator) Out[45]: False In [46]: isinstance(iter(l), Iterator) Out[46]: True

生成器

  生成器是迭代器的一种,不过生成器不需要实现__iter__()和__next__(),只要使用yield关键字返回值。当一个生成器函数调用yield,生成器函数的"状态"会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next(),yield保存的状态就被无视了。从下面的例子可以出来yield运行机制:

 In [79]: def gen():
    ...:     yield 1
    ...:     yield 2
    ...:     yield 3
    ...:

In [80]: g=gen()

In [81]: next(g)
Out[81]: 1

In [82]: next(g)
Out[82]: 2

In [83]: next(g)
Out[83]: 3

In [84]: next(g)
----------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-85-5f315c5de15b> in <module>()
----> 1 next(g)
StopIteration:

在python中生成器有两种:

  1. 生成器表达式 把列表生成式的[]中改为(),就创建一个generator In [90]: generator_expression = (x for x in range(5)) In [91]: type(generator_expression) Out[91]: generator In [92]: next(generator_expression) Out[92]: 0 In [93]: next(generator_expression) Out[93]: 1
  2. 生成器函数 In [99]: def fib(max): ...: n,a,b =0,0,1 ...: while n < max: ...: yield b ## 关键 ...: a,b =b,a+b ...: n = n+1 ...: In [102]: type(f) Out[102]: generator In [103]: print f <generator object fib at 0x1097d1cd0> In [104]: f=fib(6) In [105]: next(f) Out[105]: 1 In [106]: next(f) Out[106]: 1 In [107]: next(f) Out[107]: 2 In [108]: next(f) Out[108]: 3 从102 步骤可以看出函数fib返回一个生成器对象,函数fib在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处继续执行,也就是用多少,取多少,不占内存。

总结

  1. 函数如果定义返回的话,必须一次返回所有的结果,因为函数只能返回一次。生成器因为使用了 yield关键字,保存执行到yield的上下文,再次调用的时候可以直接继续执行下一步操作。
  2. 生成器是特殊的迭代器,只能执行一次。
  3. 生成器和迭代器两者都是可迭代对象。
  4. yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行

推荐阅读

https://www.cnblogs.com/wj-1314/p/8490822.html

http://python.jobbole.com/87613/

http://python.jobbole.com/84527/

http://python.jobbole.com/87805/

http://python.jobbole.com/87312/

https://www.zhihu.com/question/20829330

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317799226173f45ce40636141b6abc8424e12b5fb27000

https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386832360548a6491f20c62d427287739fcfa5d5be1f000

原文发表时间:2018-07-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Brian

C++ Virtual And Pure Virtual Explained

---- Virtual Virtual Function是成员函数,其行为在派生类中被覆盖。与非虚函数不同的是,即使没有关于类的实际类型的编译时信息,也会保留...

3556
来自专栏小樱的经验随笔

关于int *a[常量]与int (*a)[常量]的分析与区分(详解)

前言: 小伙伴私信我说,int *a[常量]与int (*a)[常量]这个区分不开,C指针,确实是C中最难的部分,也是学C++,JAVA,包括你以后上岗用的非常...

2723
来自专栏章鱼的慢慢技术路

多维数组的传递

1614
来自专栏PHP在线

PHP函数

请点击上面蓝色PHP关注 你知道这些简单的函数中的方法吗? count() 函数计算数组中的单元数目或对象中的属性个数。 对于数组,返回其元素的个数,对于其他值...

2955
来自专栏尾尾部落

[剑指offer] 数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复...

993
来自专栏haifeiWu与他朋友们的专栏

Python基础(一)

以#开头的语句是注释,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。

1585
来自专栏python学习指南

python列表

本篇将介绍python中的列表,更多内容请参考:Python学习指南 一、序列 在python中有六种内建的序列:列表、元祖、字符串、Unicode字符串...

3495
来自专栏深度学习思考者

Python学习(二) 正则表达式

Python正则表达式 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。re 模块使 Python 语言拥有全部的正则表达式功...

2049
来自专栏天天

数据类型的转换

1183
来自专栏desperate633

LintCode 编辑距离题目分析代码

给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。

792

扫码关注云+社区

领取腾讯云代金券