详解Python3中的迭代器和生成器及其区别


Posted in Python onOctober 09, 2018

介绍

本篇将介绍Python3中的迭代器与生成器,描述可迭代与迭代器关系,并实现自定义类的迭代器模式。

迭代的概念

上一次输出的结果为下一次输入的初始值,重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值

注:循环不是迭代

while True: #只满足重复,因而不是迭代
print('====>')

 迭代器

1.为什么要有迭代器?

对于没有索引的数据类型,必须提供一种不依赖索引的迭代方式。

2.迭代器定义:

迭代器:可迭代对象执行__iter__方法,得到的结果就是迭代器,迭代器对象有__next__方法

它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常

可迭代的(iterable)

Python标准库中存在着一些可迭代对象,例如:list, tuple, dict, set, str等。

可以对这些迭代对象,进行for-in等迭代操作,例如:

for s in "helloworld":
  print(s)

编译器若想迭代一个对象a,则会自动调用iter(a)获取该对象的迭代器(iterator),如果iter(a)抛出异常,则对象a不可迭代。

判断对象是否可迭代

原生函数iter(instance) 可以判断某个对象是否可迭代,它的工作流程大概分为以下3个步骤:

  • 检查对象instance是否实现了__iter__方法,并调用它获取返回的迭代器(iterator)。
  • 如果对象没有实现__iter__方法,但是实现了__getitem__方法,Python会生成一个迭代器。
  • 如果上述都失败,则编译器则抛出TypeError错误,‘xxx' Object is not iterable。

自定义类实现__iter__方法

根据第一条,我们自定义类Iter1实现__iter__方法使该类的对象可迭代。

class Iter1:
  def __init__(self, text):
    self.text = text

  def __iter__(self):
    return iter(self.text)

iter1 = Iter1("hello")
for s in iter1:
  print(s)

Iter1类实现了__iter__方法,通过iter()调用,得到可迭代对象text的迭代器并返回,实现了迭代器协议,因此可以通过for-in等方式对该对象进行迭代。

第二条通常都是针对Python中的序列(sequence)而定义,例如list,为了实现sequence协议,需要实现__getitem__方法。

class Iter2:
  def __init__(self, sequence):
    self.sequence = sequence

  def __getitem__(self, item):
    return self.sequence[item]


iter2 = Iter2([1, 2, 3, 4])
for s in iter2:
  print(s)

实际上,为了避免版本后序改动,Python标准库中的序列除了实现了__getitem__方法,也实现了__iter__方法,因此我们在定义序列时也应实现__iter__。

综上,如果显示判断某个对象是否可迭代,应该调用iter(instance)是否抛出异常,因为只实现了__getitem__的序列也是可迭代的(例子中Iter2的对象是可迭代的,但isinstance(iter2, abc.Iterator)返回结果是False)。同时,如果在调用iter后进行迭代操作不必显示判断,可以用try/except方式包装代码块。

iterable vs iterator(可迭代vs迭代器)

iterable定义

任何可以由原生函数iter获取到迭代器的对象
任何实现了__iter__方法并返回迭代器的对象
所有的序列(实现了__getitem__)

Python通过获取到可迭代对象的迭代器(iterator)实现迭代,例如for-in的实现其实是在内部获取到了迭代器进行操作。for-in机制可以理解为下述代码:

s = 'hello'
it = iter(s)
while (True):
  try:
    print(next(it))
  except StopIteration:
    del it
    break

StopIteration异常将在迭代器耗尽后被抛出,for-in、生成式(comprehension)、元组解压(tuple unpacking)等迭代操作都会处理并这个异常。

迭代器是个迭代值生产工厂,它保存迭代状态,并通过next()函数产生下一个迭代值。实现迭代器需要实现以下两个方法:

__iter__
返回self

__next__
返回下一个可用的元素,如果无可用元素则抛出StopIteration异常

迭代器实现__iter__,因此所有的迭代器都是可迭代的,下图展示了iterable和iterator的结构。

详解Python3中的迭代器和生成器及其区别

迭代器模式

实现一个自定义的迭代器模式需要两个类,分别为实现了__iter__方法的类和通过__iter__返回的迭代器实例类(实现了__iter__和__next__方法)。下面例子简单实现了上述功能。

class IterText:
  def __init__(self, text):
    self.text = text

  def __iter__(self):
    return IteratorText(self.text)


class IteratorText:
  def __init__(self, text):
    self.text = text
    self.index = 0

  def __iter__(self):
    return self

  def __next__(self):
    try:
      letter = self.text[self.index]
    except IndexError:
      raise StopIteration
    self.index += 1
    return letter

text = IterText("hey")
for l in text:
  print(l)

可迭代的IterText实现了__iter__方法,返回了迭代器IteratorText实例。IteratorText实现了__next__方法返回下一个迭代元素直到抛出异常,同时IteratorText实现了__iter__方法返回自身对象用于迭代。
这里的IterText和IteratorText很容易混淆,如果在IterText中实现了__next__方法并将__iter__中返回自身实例self也可以实现上述功能,但通常可迭代对象和迭代器应当分开,这样在可迭代对象中的__iter__中可以返回不同的迭代器对象,使功能独立。

生成器(generator)

通过上述文章说明,迭代器通过next()不断产出下一个元素直到迭代器耗尽,而Python中的生成器可以理解为一个更优雅的迭代器(不需要实现__iter__和__next__方法),实现了迭代器协议,它也可以通过next()产出元素。
Python中的生成器主要分为两种类型:

生成器函数(generator function)返回得到的生成器:

包含yield关键字的函数称为生成器函数

def gen_func():
  yield 1
  yield 2
  yield 3
g = gen_func()

生成器表达式(generator expression)返回得到的生成器

g = (i for i in (1, 2, 3))

我们可以利用生成器进行迭代操作:

for e in g:
  print(e)
  
## 生成器g已被耗尽,如果需要重新迭代需要重新获得新的生成器对象
g = gen_func()
for e in g:
  print(e)

利用生成器代替可迭代中的__iter__迭代器

在迭代器模式章节中,我们在可迭代IterText中的__iter__返回迭代器IteratorText实例,然而使用生成器的方式会使代码更加优雅。

class IterText:
  def __init__(self, text):
    self.text = text

  def __iter__(self):
    for letter in self.text:
      yield letter

因为yield存在于__iter__,因此__iter__变成了生成器函数,调用它测返回一个生成器,同时生成器又实现了迭代器协议,因此IterText满足了可迭代的需求。

总结

本篇介绍了Python中的可迭代(iterable)、迭代器(iterator)以及它们的关系,并讲述了迭代器模式的实现,同时通过Python中的生成器完善了迭代器模式。希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中的pprint折腾记
Jan 21 Python
Python中处理字符串之isalpha()方法的使用
May 18 Python
Python中列表、字典、元组数据结构的简单学习笔记
Mar 20 Python
python简单商城购物车实例代码
Mar 15 Python
Python拼接字符串的7种方法总结
Nov 01 Python
django小技巧之html模板中调用对象属性或对象的方法
Nov 30 Python
如何在Django中设置定时任务的方法示例
Jan 18 Python
pybind11在Windows下的使用教程
Jul 04 Python
PyCharm GUI界面开发和exe文件生成的实现
Mar 04 Python
浅谈在django中使用filter()(即对QuerySet操作)时踩的坑
Mar 31 Python
Python requests模块安装及使用教程图解
Jun 30 Python
python库sklearn常用操作
Aug 23 Python
不知道这5种下划线的含义,你就不算真的会Python!
Oct 09 #Python
详解利用django中间件django.middleware.csrf.CsrfViewMiddleware防止csrf攻击
Oct 09 #Python
详解如何将python3.6软件的py文件打包成exe程序
Oct 09 #Python
让代码变得更易维护的7个Python库
Oct 09 #Python
windows下cx_Freeze生成Python可执行程序的详细步骤
Oct 09 #Python
Python打包方法Pyinstaller的使用
Oct 09 #Python
Python如何发布程序的详细教程
Oct 09 #Python
You might like
php中可能用来加密字符串的函数[base64_encode、urlencode、sha1]
2012/01/16 PHP
PHP中模拟处理HTTP PUT请求的例子
2014/07/22 PHP
百万级别知乎用户数据抓取与分析之PHP开发
2015/09/28 PHP
JQuery UI DatePicker中z-index默认为1的解决办法
2010/09/28 Javascript
ASP.NET jQuery 实例10 动态修改hyperlink的URL值
2012/02/03 Javascript
客户端js判断文件类型和文件大小即限制上传大小
2013/11/20 Javascript
node.js中使用node-schedule实现定时任务实例
2014/06/03 Javascript
javascript 自定义回调函数示例代码
2014/09/26 Javascript
使用JavaScript链式编程实现模拟Jquery函数
2014/12/21 Javascript
JavaScript将XML转成JSON的方法
2015/03/12 Javascript
jQuery超酷平面式时钟效果代码分享
2020/03/30 Javascript
Backbone.js框架中简单的View视图编写学习笔记
2016/02/14 Javascript
jquery分页插件jquery.pagination.js实现无刷新分页
2016/04/01 Javascript
jquery mobile移动端幻灯片滑动切换效果
2020/04/15 Javascript
AngularJS使用angular.bootstrap完成模块手动加载的方法分析
2017/01/19 Javascript
解决webpack无法通过IP地址访问localhost的问题
2018/02/22 Javascript
React性能优化系列之减少props改变的实现方法
2019/01/17 Javascript
vue路由中前进后退的一些事儿
2019/05/18 Javascript
JS内置对象和Math对象知识点详解
2020/04/03 Javascript
viewer.js实现图片预览功能
2020/06/24 Javascript
Python中使用hashlib模块处理算法的教程
2015/04/28 Python
python中列表和元组的区别
2017/12/18 Python
使用python读取.text文件特定行的数据方法
2019/01/28 Python
python中文分词库jieba使用方法详解
2020/02/11 Python
浅析HTML5中的download属性使用
2019/03/13 HTML / CSS
List, Set, Map是否继承自Collection接口?
2016/05/16 面试题
XMLHttpRequest对象在IE和Firefox中创建方式有没有不同
2016/03/23 面试题
关于毕业的中学校园广播稿
2014/01/26 职场文书
法学院方阵解说词
2014/01/29 职场文书
开业主持词
2014/03/21 职场文书
工程合作意向书范本
2015/05/09 职场文书
亮剑观后感600字
2015/06/05 职场文书
《牧场之国》教学反思
2016/02/22 职场文书
jdbc使用PreparedStatement批量插入数据的方法
2021/04/27 MySQL
CSS作用域(样式分割)的使用汇总
2021/11/07 HTML / CSS
《仙剑客栈2》第一弹正式宣传片公开 年内发售
2022/04/07 其他游戏