关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任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. blast2go本地化 mysql_study

    mysql yum -y install mysql mysqladmin -uroot -passwd "oebiotech" mysql -uroot -poebiotech ...

  2. 【题解】【P3383 【模板】线性筛素数】

    看完这篇博客你就懂了 原题链接 代码: #include<bits/stdc++.h> using namespace std; bool isPrime(int num) { if(nu ...

  3. RabbitMQ(一):RabbitMQ快速入门

    RabbitMQ是目前非常热门的一款消息中间件,不管是互联网大厂还是中小企业都在大量使用.作为一名合格的开发者,有必要对RabbitMQ有所了解,本文是RabbitMQ快速入门文章. RabbitMQ ...

  4. 个人永久性免费-Excel催化剂功能第36波-新增序列函数用于生成规律性的循环重复或间隔序列

    啃过Excel函数的表哥表姐们,一定对函数的嵌套.数组公式等高级的应用有很深的体会,威力是大,但也烧死不少脑细胞,不少人就在这样的绕函数中光荣地牺牲了,走向从入门到放弃.Excel催化剂的创立,初衷就 ...

  5. 创建PaletteSet的一个问题

    下面是一个常规创建PaletteSet面板的代码: public static PaletteSet m_ps = null; [CommandMethod("MyPalette" ...

  6. 找bug的过程

    关于昨天程序出差我找bug的过程记录 昨天才程序 https://www.cnblogs.com/pythonywy/p/11006273.html ├── xxxx │ ├── src.py │ └ ...

  7. 给hexo添加宠物

    开始 之前在博客园上看到,公告栏里有人竟然在养鱼,觉得很好玩!一直念念不忘的,于是就想着在hexo中也来养几只,因为我用的事Next的Muse主题,所以有一个非常合适的侧边栏,先来看看效果. 点击此处 ...

  8. 为何出现了trx_mysql_thread_id为0 的事务是什么

    今天巡检时突然发现有很多锁等待超时的情况,原以为是一个简单的小事,一查,结果令人深思. 1.  问题现象 发现日志中出现了大量的 ERROR 1205 (HY000): Lock wait timeo ...

  9. 《VR入门系列教程》之13---相机与立体渲染

    相机.透视图.视口.投影     渲染好的场景都需要一个可以供用户查看的视图,我们通常在3D场景中用相机来提供这种需求.相机相对场景有位置和方向,就像我们生活中的相机一样,它也提供透视图查看方式,这种 ...

  10. 小白学python-day05-IDE、格式化输出、For While循环、断点、continue、break

    今天是day05,以下是学习总结. 但行努力,莫问前程. ----------------------------------------------------------------------- ...