关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

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中有哪些常见的可迭代对象呢?

  1. 集合或序列类型(如listtuplesetdictstr
  2. 文件对象
  3. 在类中定义了__iter__()方法的对象,可以被认为是 Iterable对象,但自定义的可迭代对象要能在for循环中正确使用,就需要保证__iter__()实现必须是正确的(即可以通过内置iter()函数转成Iterator对象。关于Iterator下文还会说明,这里留下一个坑,只是记住iter()函数是能够将一个可迭代对象转成迭代器对象,然后在for中使用)
  4. 在类中实现了如果只实现__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中使用的对象,不一定是可迭代对象。

现在我们做个小结:

  1. 一个可迭代的对象是实现了__iter__()方法的对象
  2. 它要在for循环中使用,就必须满足iter()的调用(即调用这个函数不会出错,能够正确转成一个Iterator对象)
  3. 可以通过已知的可迭代对象来辅助实现我们自定义的可迭代对象。
  4. 一个对象实现了__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)

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

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

定义生成器有两种方式:

  1. 列表生成器
  2. 使用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 引用

  1. https://docs.python.org/3.7/

一文搞懂Python可迭代、迭代器和生成器的概念的更多相关文章

  1. 一文搞懂Python迭代器和生成器

    很多童鞋搞不懂python迭代器和生成器到底是什么?它们之间又有什么样的关系? 这篇文章就是要用最简单的方式让你理解Python迭代器和生成器! 1.迭代器和迭代过程 维基百科解释道: 在Python ...

  2. 一文搞懂Python Unittest测试方法执行顺序

    大家好~我是米洛! 欢迎关注我的公众号测试开发坑货,一起交流!点赞收藏关注,不迷路. Unittest unittest大家应该都不陌生.它作为一款博主在5-6年前最常用的单元测试框架,现在正被pyt ...

  3. 一文搞懂 Python 的模块和包,在实战中的最佳实践

    最近公司有个项目,我需要写个小爬虫,将爬取到的数据进行统计分析.首先确定用 Python 写,其次不想用 Scrapy,因为要爬取的数据量和频率都不高,没必要上爬虫框架.于是,就自己搭了一个项目,通过 ...

  4. 一文搞懂Python函数(匿名函数、嵌套函数、闭包、装饰器)!

    Python函数定义.匿名函数.嵌套函数.闭包.装饰器 目录 Python函数定义.匿名函数.嵌套函数.闭包.装饰器 函数核心理解 1. 函数定义 2. 嵌套函数 2.1 作用 2.2 函数变量作用域 ...

  5. 一文搞懂Python中的所有数组数据类型

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  6. 一文搞懂所有Java集合面试题

    Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...

  7. 一文搞懂Flink Window机制

    Windows是处理无线数据流的核心,它将流分割成有限大小的桶(buckets),并在其上执行各种计算. 窗口化的Flink程序的结构通常如下,有分组流(keyed streams)和无分组流(non ...

  8. 一文搞懂RAM、ROM、SDRAM、DRAM、DDR、flash等存储介质

    一文搞懂RAM.ROM.SDRAM.DRAM.DDR.flash等存储介质 存储介质基本分类:ROM和RAM RAM:随机访问存储器(Random Access Memory),易失性.是与CPU直接 ...

  9. 基础篇|一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

随机推荐

  1. 20190101.DDD笔记

    建立领域模型步骤 根据提供的信息完善主要业务场景和业务流程: 根据业务流程识别领域事件并按照时序排列: 针对领域事件进行命令识别: 针对领域事件和命令进行聚合和子域的初步识别: 在识别的subdoma ...

  2. 20131201-插件-XML-第十二天(未完)

    以后再写代码的时候,先从中间层|接口|协议开始入手. 在写XML时注意的事情: 在EditPlus中,Tab是缩进 在头文件中的编码格式是"utf-8"是,在Editplus中保存 ...

  3. Java核心技术中的程序片段

    import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import ...

  4. Excel催化剂开源第44波-窗体在Show模式下受Excel操作影响变为最小化解决方式

    在Excel催化剂的许多功能中,都会开发窗体用于给用户更友好的交互使用,但有一个问题,困扰许久,在窗体上运行某些代码后,中途弹出下MessageBox对话框给用户做一些简单的提示或交互时,发现程序运行 ...

  5. k8s1.9.0安装--基础集群部署

    二.基础集群部署 - kubernetes-simple 1. 部署ETCD(主节点) 1.1 简介 kubernetes需要存储很多东西,像它本身的节点信息,组件信息,还有通过kubernetes运 ...

  6. 【杂谈】Hash表与平衡树

    hash表与平衡树查询数据的时间复杂度是多少? hash表为O(1),平衡树为O(logn) 这个时间复杂度是如何得出的? 时间复杂度是按照最糟糕的情况来的.但即使是最糟糕的情况,hash表也只需要计 ...

  7. Spring Boot 面试的十个问题

    用下面这些常见的面试问题为下一次 Spring Boot 面试做准备. 在本文中,我们将讨论 Spring boot 中最常见的10个面试问题.现在,在就业市场上,这些问题有点棘手,而且趋势日益严重. ...

  8. Kotlin学习快速入门(3)——类 继承 接口

    类 参考链接 类定义格式 使用class关键字定义,格式如下: class T{ //属性 //构造函数 //函数 //内部类 } Java Bean类 java bean类 //java bean类 ...

  9. 使用Java实现数据库编程 项目(宠物商店)

    创建数据库代码: DROP DATABASE IF EXISTS petShop; CREATE DATABASE petShop; USE petShop; /*创建表*/ CREATE TABLE ...

  10. HTTP_2_HTTP协议概要

    http协议概要 HTTP 通信对象 通信方式 通信状态 定位资源 节省通信量 超文本传输协议 客户端与服务器端 请求和响应 不保存状态(借助cookie) 请求URI keep-alive/pipe ...