一篇文章彻底搞懂Python中可迭代(Iterable)、迭代器(Iterator)与生成器(Generator)的概念


Posted in Python onMay 13, 2019

前言

在Python中可迭代(Iterable)、迭代器(Iterator)和生成器(Generator)这几个概念是经常用到的,初学时对这几个概念也是经常混淆,现在是时候把这几个概念搞清楚了。

0x00 可迭代(Iterable)

简单的说,一个对象(在Python里面一切都是对象)只要实现了只要实现了__iter__()方法,那么用isinstance()函数检查就是Iterable对象;

例如

class IterObj:
 
 def __iter__(self):
  # 这里简单地返回自身
  # 但实际情况可能不会这么写
  # 而是通过内置的可迭代对象来实现
  # 下文的列子中将会展示
  return self

上面定义了一个类IterObj并实现了__iter__()方法,这个就是一个可迭代(Iterable)对象

it = IterObj()
 print(isinstance(it, Iterable)) # true
 print(isinstance(it, Iterator)) # false
 print(isinstance(it, Generator)) # false

记住这个类,下文我们还会看到这个类的定义。

常见的可迭代对象

在Python中有哪些常见的可迭代对象呢?

  • 集合或序列类型(如list、tuple、set、dict、str)
  • 文件对象
  • 在类中定义了__iter__()方法的对象,可以被认为是 Iterable对象,但自定义的可迭代对象要能在for循环中正确使用,就需要保证__iter__()实现必须是正确的(即可以通过内置iter()函数转成Iterator对象。关于Iterator下文还会说明,这里留下一个坑,只是记住iter()函数是能够将一个可迭代对象转成迭代器对象,然后在for中使用)
  • 在类中实现了如果只实现__getitem__()的对象可以通过iter()函数转化成迭代器但其本身不是可迭代对象。所以当一个对象能够在for循环中运行,但不一定是Iterable对象。

关于第1、2点我们可以通过以下来验证

print(isinstance([], Iterable)) # true list 是可迭代的
 print(isinstance({}, Iterable)) # true 字典是可迭代的
 print(isinstance((), Iterable)) # true 元组是可迭代的
 print(isinstance(set(), Iterable)) # true set是可迭代的
 print(isinstance('', Iterable)) # true 字符串是可迭代的
 
 currPath = os.path.dirname(os.path.abspath(__file__))
 with open(currPath+'/model.py') as file:
  print(isinstance(file, Iterable)) # true

我们再来看第3点,

print(hasattr([], "__iter__")) # true
 print(hasattr({}, "__iter__")) # true
 print(hasattr((), "__iter__")) # true
 print(hasattr('', "__iter__")) # true

这些内置集合或序列对象都有__iter__属性,即他们都实现了同名方法。但这个可迭代对象要在for循环中被使用,那么它就应该能够被内置的iter()函数调用并转化成Iterator对象。

例如,我们看内置的可迭代对象

print(iter([])) # <list_iterator object at 0x110243f28>
 print(iter({})) # <dict_keyiterator object at 0x110234408>
 print(iter(())) # <tuple_iterator object at 0x110243f28>
 print(iter('')) # <str_iterator object at 0x110243f28>

它们都相应的转成了对应的迭代器(Iterator)对象。

现在回过头再看看一开始定义的那个IterObj类

class IterObj:
 
 def __iter__(self):
  return self 
  
it = IterObj()
print(iter(it))

我们使用了iter()函数,这时候将再控制台上打印出以下信息:

Traceback (most recent call last):
  File "/Users/mac/PycharmProjects/iterable_iterator_generator.py", line 71, in <module>
    print(iter(it))
TypeError: iter() returned non-iterator of type 'IterObj'

出现了类型错误,意思是iter()函数不能将‘非迭代器'类型转成迭代器。

那如何才能将一个可迭代(Iterable)对象转成迭代器(Iterator)对象呢?

我们修改一下IterObj类的定义

class IterObj:

 def __init__(self):
  self.a = [3, 5, 7, 11, 13, 17, 19]

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

我们在构造方法中定义了一个名为a的列表,然后还实现了__iter__()方法。

修改后的类是可以被iter()函数调用的,即也可以在for循环中使用

it = IterObj()
 print(isinstance(it, Iterable)) # true
 print(isinstance(it, Iterator)) # false
 print(isinstance(it, Generator)) # false
 print(iter(it)) # <list_iterator object at 0x102007278>
 for i in it:
  print(i) # 将打印3、5、7、11、13、17、19元素

因此在定义一个可迭代对象时,我们要非常注意__iter__()方法的内部实现逻辑,一般情况下,是通过一些已知的可迭代对象(例如,上文提到的集合、序列、文件等或其他正确定义的可迭代对象)来辅助我们来实现

关于第4点说明的意思是iter()函数可以将一个实现了__getitem__()方法的对象转成迭代器对象,也可以在for循环中使用,但是如果用isinstance()方法来检测时,它不是一个可迭代对象。

class IterObj:
 
 def __init__(self):
  self.a = [3, 5, 7, 11, 13, 17, 19]
 
 def __getitem__(self, i):
  return self.a[i]
  
it = IterObj()
print(isinstance(it, Iterable)) # false
print(isinstance(it, Iterator)) # false
print(isinstance(it, Generator)) false
print(hasattr(it, "__iter__")) # false
print(iter(it)) # <iterator object at 0x10b231278>

for i in it:
 print(i) # 将打印出3、5、7、11、13、17、19

这个例子说明了可以在for中使用的对象,不一定是可迭代对象。

现在我们做个小结:

  • 一个可迭代的对象是实现了__iter__()方法的对象
  • 它要在for循环中使用,就必须满足iter()的调用(即调用这个函数不会出错,能够正确转成一个Iterator对象)
  • 可以通过已知的可迭代对象来辅助实现我们自定义的可迭代对象。
  • 一个对象实现了__getitem__()方法可以通过iter()函数转成Iterator,即可以在for循环中使用,但它不是一个可迭代对象(可用isinstance方法检测())

0x01 迭代器(Iterator)

上文很多地方都提到了Iterator,现在我们把这个坑填上。

当我们对可迭代的概念了解后,对于迭代器就比较好理解了。

一个对象实现了__iter__()和__next__()方法,那么它就是一个迭代器对象。 例如

class IterObj:

 def __init__(self):
  self.a = [3, 5, 7, 11, 13, 17, 19]

  self.n = len(self.a)
  self.i = 0

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

 def __next__(self):
  while self.i < self.n:
   v = self.a[self.i]
   self.i += 1
   return v
  else:
   self.i = 0
   raise StopIteration()

在IterObj中,构造函数中定义了一个列表a,列表长度n,索引i。

it = IterObj()
 print(isinstance(it, Iterable)) # true
 print(isinstance(it, Iterator)) # true
 print(isinstance(it, Generator)) # false
 print(hasattr(it, "__iter__")) # true
 print(hasattr(it, "__next__")) # true

我们可以发现上文提到的

集合和序列对象是可迭代的但不是迭代器

print(isinstance([], Iterator)) # false
 print(isinstance({}, Iterator)) # false
 print(isinstance((), Iterator)) # false
 print(isinstance(set(), Iterator)) # false
 print(isinstance('', Iterator)) # false

而文件对象是迭代器

currPath = os.path.dirname(os.path.abspath(__file__))
 with open(currPath+'/model.py') as file:
  print(isinstance(file, Iterator)) # true

一个迭代器(Iterator)对象不仅可以在for循环中使用,还可以通过内置函数next()函数进行调用。 例如

it = IterObj()
next(it) # 3
next(it) # 5

0x02 生成器(Generator)

现在我们来看看什么是生成器?

一个生成器既是可迭代的也是迭代器

定义生成器有两种方式:

  • 列表生成器
  • 使用yield定义生成器函数

先看第1种情况

g = (x * 2 for x in range(10)) # 0~18的偶数生成器 
 print(isinstance(g, Iterable)) # true
 print(isinstance(g, Iterator)) # true
 print(isinstance(g, Generator)) # true
 print(hasattr(g, "__iter__")) # true
 print(hasattr(g, "__next__")) # true
 print(next(g)) # 0
 print(next(g)) # 2

列表生成器可以不需要消耗大量的内存来生成一个巨大的列表,只有在需要数据的时候才会进行计算。

再看第2种情况

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

这里yield的作用就相当于return,这个函数就是顺序地返回[0,10)的之间的自然数,可以通过next()或使用for循环来遍历。

当程序遇到yield关键字时,这个生成器函数就返回了,直到再次执行了next()函数,它就会从上次函数返回的执行点继续执行,即yield退出时保存了函数执行的位置、变量等信息,再次执行时,就从这个yield退出的地方继续往下执行。

在Python中利用生成器的这些特点可以实现协程。协程可以理解为一个轻量级的线程,它相对于线程处理高并发场景有很多优势。

看下面一个用协程实现的生产者-消费者模型

def producer(c):
 n = 0
 while n < 5:
  n += 1
  print('producer {}'.format(n))
  r = c.send(n)
  print('consumer return {}'.format(r))


def consumer():
 r = ''
 while True:
  n = yield r
  if not n:
   return
  print('consumer {} '.format(n))
  r = 'ok'


if __name__ == '__main__':
 c = consumer()
 next(c) # 启动consumer
 producer(c)

这段代码执行效果如下

producer 1
consumer 1
producer return ok
producer 2
consumer 2
producer return ok
producer 3
consumer 3
producer return ok

协程实现了CPU在两个函数之间进行切换从而实现并发的效果。

0x04 引用

docs.python.org/3.7/

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python计数排序和基数排序算法实例
Apr 25 Python
pymongo实现多结果进行多列排序的方法
May 16 Python
Python实现将目录中TXT合并成一个大TXT文件的方法
Jul 15 Python
Python中集合的内建函数和内建方法学习教程
Aug 19 Python
python+Django+apache的配置方法详解
Jun 01 Python
python3制作捧腹网段子页爬虫
Feb 12 Python
Python对列表中的各项进行关联详解
Aug 15 Python
python pandas读取csv后,获取列标签的方法
Nov 12 Python
Python基础之函数的定义与使用示例
Mar 23 Python
Python使用正则表达式分割字符串的实现方法
Jul 16 Python
基于多进程中APScheduler重复运行的解决方法
Jul 22 Python
python tkinter之顶层菜单、弹出菜单实例
Mar 04 Python
为什么你还不懂得怎么使用Python协程
May 13 #Python
Python玩转加密的技巧【推荐】
May 13 #Python
11个Python3字典内置方法大全与示例汇总
May 13 #Python
python中的数据结构比较
May 13 #Python
Python中函数的基本定义与调用及内置函数详解
May 13 #Python
python实现弹跳小球
May 13 #Python
Python开发之Nginx+uWSGI+virtualenv多项目部署教程
May 13 #Python
You might like
图书管理程序(一)
2006/10/09 PHP
php 应用程序安全防范技术研究
2009/09/25 PHP
sphinx增量索引的一个问题
2011/06/14 PHP
如何利用php array_multisort函数 对数据库结果进行复杂排序
2013/06/08 PHP
PHP简单实现模拟登陆功能示例
2017/09/15 PHP
JavaScript 拾碎[三] 使用className属性
2010/10/16 Javascript
javascript 节点排序 2
2011/01/31 Javascript
Js-$.extend扩展方法使方法参数更灵活
2013/01/15 Javascript
Windows系统下使用Sublime搭建nodejs环境
2015/04/13 NodeJs
jQuery中 delegate使用的问题
2015/07/03 Javascript
jQuery的文档处理程序详解
2016/05/10 Javascript
微信小程序 canvas API详解及实例代码
2016/10/08 Javascript
微信小程序的日期选择器的实例详解
2017/09/29 Javascript
Vue下的国际化处理方法
2017/12/18 Javascript
JS中循环遍历数组的四种方式总结
2021/01/23 Javascript
[02:53]DOTA2英雄昆卡基础教程
2013/11/25 DOTA
[55:04]海涛DOTA2死魂复燃6.82版本介绍
2014/09/28 DOTA
[54:33]2018DOTA2亚洲邀请赛小组赛 A组加赛 Liquid vs Optic
2018/04/03 DOTA
详解python的数字类型变量与其方法
2016/11/20 Python
python学习必备知识汇总
2017/09/08 Python
学习Python3 Dlib19.7进行人脸面部识别
2018/01/24 Python
Python3连接SQLServer、Oracle、MySql的方法
2018/06/28 Python
python 自定义异常和异常捕捉的方法
2018/10/18 Python
pycharm 在windows上编辑代码用linux执行配置的方法
2018/10/27 Python
python 找出list中最大或者最小几个数的索引方法
2018/10/30 Python
python实现移动木板小游戏
2020/10/09 Python
opencv+pyQt5实现图片阈值编辑器/寻色块阈值利器
2020/11/13 Python
详解CSS3浏览器兼容
2016/12/14 HTML / CSS
HTML页面中添加Canvas标签示例
2015/01/01 HTML / CSS
Pretty Green美国:英式摇滚服饰风格代表品牌之一
2019/01/23 全球购物
什么是Smarty变量操作符?如何使用Smarty变量操作符
2014/07/18 面试题
新学期家长寄语
2014/01/19 职场文书
七年级数学教学反思
2014/01/22 职场文书
人力资源管理专业自荐书范文
2014/02/10 职场文书
使用HTML+Css+transform实现3D导航栏的示例代码
2021/03/31 HTML / CSS
手残删除python之后的补救方法
2021/06/26 Python