一篇文章彻底搞懂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 相关文章推荐
Pandas 对Dataframe结构排序的实现方法
Apr 10 Python
python实现寻找最长回文子序列的方法
Jun 02 Python
python集合比较(交集,并集,差集)方法详解
Sep 13 Python
在ubuntu16.04中将python3设置为默认的命令写法
Oct 31 Python
Python 按字典dict的键排序,并取出相应的键值放于list中的实例
Feb 12 Python
Python中如何使用if语句处理列表实例代码
Feb 24 Python
解决django中ModelForm多表单组合的问题
Jul 18 Python
Python 实现OpenCV格式和PIL.Image格式互转
Jan 09 Python
python3通过qq邮箱发送邮件以及附件
May 20 Python
python程序实现BTC(比特币)挖矿的完整代码
Jan 20 Python
Python基础之变量的相关知识总结
Jun 23 Python
Django REST framework 限流功能的使用
Jun 24 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
PHP原生函数一定好吗?
2014/12/08 PHP
验证坐标在某坐标区域内php代码
2016/10/08 PHP
基于prototype的validation.js发布2.3.4新版本,让你彻底脱离表单验证的烦恼
2006/12/06 Javascript
$()JS小技巧
2007/07/21 Javascript
js停止输出代码
2008/07/20 Javascript
js 关键词高亮(根据ID/tag高亮关键字)案例介绍
2013/01/21 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
Javascript基础教程之argument 详解
2015/01/18 Javascript
Javascript核心读书有感之类型、值和变量
2015/02/11 Javascript
JavaScript使用function定义对象并调用的方法
2015/03/23 Javascript
jQuery实现的AJAX简单弹出层效果代码
2015/11/26 Javascript
GitHub上一些实用的JavaScript的文件压缩解压缩库推荐
2016/03/13 Javascript
详解百度百科目录导航树小插件
2017/01/08 Javascript
jQuery实现动态添加、删除按钮及input输入框的方法
2017/04/27 jQuery
vue awesome swiper异步加载数据出现的bug问题
2018/07/03 Javascript
详解Nuxt.js 实战集锦
2019/11/19 Javascript
Websocket 向指定用户发消息的方法
2020/01/09 Javascript
vue 路由meta 设置导航隐藏与显示功能的示例代码
2020/09/04 Javascript
python dict remove数组删除(del,pop)
2013/03/24 Python
Django后台获取前端post上传的文件方法
2018/05/28 Python
Python设计模式之外观模式实例详解
2019/01/17 Python
OpenCV HSV颜色识别及HSV基本颜色分量范围
2019/03/22 Python
python GUI库图形界面开发之PyQt5 Qt Designer工具(Qt设计师)详细使用方法及Designer ui文件转py文件方法
2020/02/26 Python
python如何调用字典的key
2020/05/25 Python
5款实用的python 工具推荐
2020/10/13 Python
详解Python遍历列表时删除元素的正确做法
2021/01/07 Python
纯CSS实现的大小渐变、渐远效果
2014/04/15 HTML / CSS
浅谈Html5页面打开app的一些思考
2020/03/30 HTML / CSS
大唐面试试题(CPU,UNIX等等)
2012/01/11 面试题
一封普通求职者的求职信
2013/11/20 职场文书
司机检讨书
2014/02/13 职场文书
2014财务人员自我评价范文
2014/09/21 职场文书
四则混合运算教学反思
2016/02/23 职场文书
PHP解决高并发问题
2021/04/01 PHP
Python实现老照片修复之上色小技巧
2021/10/16 Python
Python标准库pathlib操作目录和文件
2021/11/20 Python