很多Python的程序员都会混淆 迭代器生成器 的概念和作用,分不清到底两个有什么区别。今天我们来好好说一说这两个概念。

迭代器(Iterator)

Iterator Pattern

Iterator 是一种设计模式,它的作用是,提供一种顺序访问一个聚合对象中的各个元素,但又不需要暴露出其内部实现的方法。它是一种惰性的获取数据的方法,我们不需要一次把所有的数据载入内存,这样可以避免数据集太大,内存无法全部装载的麻烦。
这种应用场景,比如:读取一个大文件,分析每一行的关键字

一个最简单的迭代器模式,表现为一个接口,接口中包含两个方法:

  1. Next() 返回下一个元素
  2. hasNext() 返回是否还有下一个元素

实现了这种两个方法的对象就是一个迭代器。

Python中的Iterator

在很多时候,Python程序员会忽略 迭代器(Iterator) 和 可迭代对象(Iterable Object) 的区别。

其实,我们要好好的区分一下他们两个。

可迭代对象(Iterable Object)

可迭代对象是表示一个对象,拥有一次返回一个他自己的数据元素的能力。

例如:

In [1]: a = [1, 2, 3, 4, 5]

In [2]: for i in a:
...: print(i)
...:
1
2
3
4
5 In [3]: b = {"first":1, "second":2, "third":3} In [4]: for i in b:
...: print i
...:
second
third
first

上面的代码通过迭代的方式输出了list中所有的元素,和dict中所有的key。所以,我们把list和dict叫做可迭代对象(不是迭代器)。

在Python中,所有的集合都可以迭代。在语言内部,迭代器支持下面列出的操作:

  • for循环
  • 遍历文件、目录
  • 列表推导、字典推导和集合推导
  • 元组拆包
  • 调用函数时,使用 * 拆包实参
  • 构建和扩展集合类型

所以可以看到,迭代操作在python中很多地方都很重要。

序列可以迭代的原因

这依赖一个buildin-function iter()。假如解释器要迭代对象x,则会调用 iter() 产生一个迭代器,进行迭代。

内置的 iter 函数有以下作用:

  1. 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
  2. 如果没有实现 __iter__ 方法, 但是实现了 __getitem__ 方法, Python 会创建一个迭代 器,尝试按顺序(从索引 0 开始)获取元素。
  3. 如果尝试失败, Python 抛出 TypeError 异常, 通常会提示“X object is not iterable”。
In [8]: x = 2
In [9]: iter(x)
-----------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-128770259dbe> in <module>()
----> 1 iter(x) TypeError: 'int' object is not iterable

标准序列都实现了 __getitem__ 方法。 其实,它们也都实现了 __iter__ 方法,因此你也应该这么做。之所以都实现 __getitem__ 是因为要向后兼容,但后续可能废弃。

如何实现可迭代对象

自己创建的 Object 如何变成一个可迭代的对象呢?如何自己创建一个迭代器呢?其实非常简单。

对于可迭代对象,需要满足下面两个要求的任意一个(原因参见上面):

  1. 拥有 __getitem__ 方法;接受一个参数 index
  2. 拥有 __iter__ 方法;返回一个 Iterator

例:

#!/usr/bin/env python

class MyIterableObject():

    def __init__(self, s):
self.seq = s.split(' ') def __getitem__(self, index):
return self.seq[index] def __iter__(self):
return MyIterator(self.seq) # MyIterator的具体实现参见后面 if __name__ == '__main__': mio = MyIterableObject("a b c d e f g") for i in mio:
print(i)

迭代器(Iterator)

当用 iter 函数获取到一个迭代器之后,就可以操作迭代器来获取对象的数据了。

使用 next() 方法来一个个的获取元素。当所有元素获取完毕,继续调用 next() 方法的话,就会抛出一个 StopIteration 的异常。

如下:

In [13]: a = [1, 2, 3, 4, 5]
In [14]: i = iter(a)
In [15]: while True:
...: print(next(i))
...:
1
2
3
4
5
-----------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-15-ac43f8f9aeeb> in <module>()
1 while True:
----> 2 print(next(i))
3 StopIteration:

python的迭代器较为简单,它并不支持重新定位到开始这样的操作。如果一个迭代器一旦开始使用,如果想要从最开始读取的话,只能创建一个新的迭代器了。

如何实现迭代器

标准的python迭代器需要实现两个方法:

  1. __iter__ 返回迭代器本身
  2. next() 返回数据集中的下一个元素。如果没有下一个了,则抛出一个 StopIteration

TIPS:
在 python3中 next() 方法的名称,改为了 __next__ ,但是使用 python2 的方式依然可行。

例:


class MyIterator(): def __init__(self, s): self.seq = s
self.len = len(self.seq)
self.index = 0 def __iter__(self):
return self def next(self):
try:
n = self.seq[self.index]
except IndexError:
raise StopIteration self.index += 1 return n

这里有一点需要注意:迭代器模式描述中,需要有一个方法来判断是否是最后一个元素,在python中使用异常代替了这个函数。在我们使用迭代器的过程中,捕获这个异常即可。如果使用 buildin 的 for .. in 方式的话,它会自动帮我们捕获。

生成器(Generator)

首先,我们平常说起来 Generator 这个东西的时候,其实,它一般指代两个东西:

  1. Generator Function: 一个函数,在定义时使用了 yield 关键字,则成为这个函数为 生成器函数
  2. Generator Object: 由 Generator Function 生成的,是一个特殊的 Iterator。它包装了 生成器函数 的定义体,并实现了 __iter__next 两个方法,符合 Iterator 的协议。

生成器和迭代器最大的不同在哪里呢?

主要是对于值产生的方法不一样。当使用迭代器时,所有要迭代的元素必须是已经存在的。而对于生成器来说,每个值不必已经存在,可以在执行的过程中计算(生成)出来。

比如:用生成器 生成一个等比数列

def arithmetic_progression(base, dif, count):
for n in range(count):
yield base + dif * n if __name__ == '__main__': for i in arithmetic_progression(1, 3, 10):
print(i)

可以看到这个等比数列是不存在的,是在迭代的过程中每次执行到 yield 的时候,计算出来的。

可以达到这样的特性归功于 yield 关键字。它可以将执行的函数暂停,并返回值,下一次从中断的地方继续。它的执行流程如下:

  1. 使用 next 调用 生成器函数
  2. 函数 执行到 yield,会返回一个值,并暂停函数
  3. 重复 1-2 步,直到所有的值都返回完毕
  4. 如果使用 next,则会抛出 StopIteration

代码验证如下:

In [21]: def test():
...: yield 1
...: yield 2
...: yield 3
...: In [22]: gen = test() In [23]: next(gen)
Out[23]: 1 In [24]: next(gen)
Out[24]: 2 In [25]: next(gen)
Out[25]: 3 In [26]: next(gen)
-----------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-26-8a6233884a6c> in <module>()
----> 1 next(gen) StopIteration:

用生成器代替迭代器

现在我们用生成器来替代上面的 迭代器方案 MyIterableObject

class MyGenerator():

    def __init__(self, s):
self.seq = s.split(' ') def __iter__(self):
for s in self.seq:
yield s

代码简化了很多,我们不需要再自己创建 Iterator 对象,yield会帮我们做这些。

迭代器工具集(itertools)

虽然,生成器的使用已经够简单了,但是像python这种节省你生命时间的语言,怎么会没有更进一步的包装出来?

python内置了非常多的生成器函数,比如遍历文件夹的 os.walk,工具类的有 mapenumerate 等等。

python还有一个官方库,叫做 itertools,它包含了 19个 生成器函数,可以组合完成各样的功能。

结尾

以上,就是 迭代器和生成器的区别。其实,这两个东西并不难理解。但是,这里面有几个比较容易混淆的概念。只要搞清楚了这些概念,就能区分得很清楚啦!


作者和出处(reposkeeper) 授权分享 By CC BY-SA 4.0

关注微信公众号,获取新文章的推送!

Python系列之 迭代器和生成器的更多相关文章

  1. python设计模式之迭代器与生成器详解(五)

    前言 迭代器是设计模式中的一种行为模式,它提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示.python提倡使用生成器,生成器也是迭代器的一种. 系列文章 python设计模 ...

  2. python is、==区别;with;gil;python中tuple和list的区别;Python 中的迭代器、生成器、装饰器

    1. is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同 == 比较的是两个对象的内容是否相等 2. with语句时用于对try except finally 的优 ...

  3. Python 闭包、迭代器、生成器、装饰器

    Python 闭包.迭代器.生成器.装饰器 一.闭包 闭包:闭包就是内层函数对外层函数局部变量的引用. def func(): a = "哈哈" def func2(): prin ...

  4. 1.17 Python基础知识 - 迭代器和生成器初识

    可循环迭代的对象称为可迭代对象,迭代器和生成器函数是可迭代对象. 列表解析表达式:可以简单高效处理一个可迭代对象,并生成结果列表 示例代码: [ i ** 2 for i in range(10) ] ...

  5. Python中的迭代器和生成器

    本文以实例详解了python的迭代器与生成器,具体如下所示: 1. 迭代器概述: 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后 ...

  6. [js高手之路] es6系列教程 - 迭代器与生成器详解

    什么是迭代器? 迭代器是一种特殊对象,这种对象具有以下特点: 1,所有对象都有一个next方法 2,每次调用next方法,都会返回一个对象,该对象包含两个属性,一个是value, 表示下一个将要返回的 ...

  7. Python基础之迭代器和生成器

    阅读目录 楔子 python中的for循环 可迭代协议 迭代器协议 为什么要有for循环 初识生成器 生成器函数 列表推导式和生成器表达式 本章小结 生成器相关的面试题 返回顶部 楔子 假如我现在有一 ...

  8. Python学习之迭代器和生成器

    那么首先什么是迭代器和生成器呢? 迭代器即迭代的工具,那么什么又是迭代呢?所谓迭代:迭代是一个重复的过程,每次重读即一次迭代,并且每次迭代的结果都是下一次迭代的初始值.例: l=[1,2,3] cou ...

  9. python基础8 -----迭代器和生成器

    迭代器和生成器 一.迭代器 1.迭代器协议指的是对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2. ...

随机推荐

  1. Java学习---下载文件并且对文件编码

    import java.io.IOException; import java.net.URLEncoder; import sun.misc.BASE64Encoder; public class ...

  2. 深入浅出SharePoint2010——请假系统实战

    一.需求分析文档 二.请假系统无代码解决方案 2.1 数据框架设计 2.2 权限设计 2.3 表单设计 2.4 工作流设计 2.5 门户主页设计 三.请假系统第三方解决方案(Nintex) 四.请假系 ...

  3. 由JDK源码学习ArrayList

    ArrayList是实现了List接口的动态数组.与java中的数组相比,它的容量能动态增长.ArrayList的三大特点: ① 底层采用数组结构 ② 有序 ③ 非同步 下面我们从ArrayList的 ...

  4. vim基础初步

    vim文本编辑器初步 一.跟vi编辑器的关系 可以说vim编辑器是vi编辑器的升级版,它保留了vi编辑器的所有东西,而且加入了自己的新的特性. 比如说:支持跨平台,支持语法高亮,支持多级撤销等. ++ ...

  5. C++ 异常处理机制的实现

    http://blog.jobbole.com/103925/ 本文深入讨论了VC++编译器异常处理的实现机制.附件源代码包含了一个VC++的异常处理库. 下载源代码 – 19 Kb 介绍 相对于传统 ...

  6. [CTSC2018]假面

    题目 先来考虑一下第一问,血量有\(P\)的概率减\(1\) 由于我们最后需要求每一个人的期望血量,于是考虑维护出每个人处于不同血量时候的概率 一个简单\(dp\)即可 \[dp_{i,j}=dp_{ ...

  7. Kali-linux使用Wifite破解无线网络

    一些破解无线网络程序是使用Aircrack-ng工具集,并添加了一个图形界面或使用文本菜单的形式来破解无线网络.这使得用户使用它们更容易,而且不需要记住任何命令.本节将介绍使用命令行工具Wifite, ...

  8. EF中連表查詢的應用方式

    1.首先我們想讓列表頁顯示兩個表的共同數據 這裡有兩張表 public class mytype    {        public int mytypeID { get; set; }       ...

  9. QTP基本方法2------截取字符串

    1.instr: 返回字符串在另外一个字符串中第一次出现的位置 结构:instr([start],string1,string2[,compare]) start:开始位置,可选参数,默认为1 str ...

  10. [转]Ribbon界面介绍(1)

    小弟最近在学习VS2010中Ribbon界面的介绍,相比与C#的界面设计的强大,C++的界面实在太难做了,但没办法,项目需求,又不得不做,遍查网络上的资料,发现有用的基本上就是MSDN的帮助,又是全英 ...