1 引言

只要你学了Python语言,就不会不知道for循环,也肯定用for循环来遍历一个列表(list),那为什么for循环可以遍历list,而不能遍历int类型对象呢?怎么让一个自定义的对象可遍历?

这篇博客中,我们来一起探索一下这个问题,在这个过程中,我们会介绍到迭代器、可迭代对象、生成器,更进一步的,我们会详细介绍他们的原理、异同。

2 迭代器与可迭代对象

在开始下面内容之前,我们先说说标题中的“迭代”一词。什么是迭代?我认为,迭代一个完整过程中的一个重复,或者说每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,举一个类比来说:一个人类家族的发展是一个完整过程,需要经过数代人的努力,每一代都会以接着上一代的成果继续发展,所以每一代都是迭代。

2.1 迭代器

(1)怎么判断是否可迭代

作为一门设计语言,Python提供了许多必要的数据类型,例如基本数据类型int、bool、str,还有容器类型list、tuple、dict、set。这些类型当中,有些是可迭代的,有些不可迭代,怎么判断呢?

在Python中,我们把所有可以迭代的对象统称为可迭代对象,有一个类专门与之对应:Iterable。所以,要判断一个类是否可迭代,只要判断是否是Iterable类的实例即可。

>>> from collections importIterable>>> isinstance(123, Iterable)

False>>>isinstance(True, Iterable)

False>>> isinstance('abc', Iterable)

True>>>isinstance([], Iterable)

True>>>isinstance({}, Iterable)

True>>>isinstance((), Iterable)

True

所以,整型、布尔不可迭代,字符串、列表、字典、元组可迭代。

怎么让一个对象可迭代呢?毕竟,很多时候,我们需要用到的对象不止Python内置的这些数据类型,还有自定义的数据类型。答案就是实现__iter__()方法,只要一个对象定义了__iter__()方法,那么它就是可迭代对象。

from collections.abc importIterableclassA():def __iter__(self):pass

print('A()是可迭代对象吗:',isinstance(A(),Iterable))

结果输出为:

A()是可迭代对象吗: True

瞧,我们在__iter__()方法里面甚至没写任何东西,反正我们在类A中定义则__iter__()方法,那么,它就是一个可迭代对象。

重要的事情说3遍:

只要一个对象定义了__iter__()方法,那么它就是可迭代对象。

只要一个对象定义了__iter__()方法,那么它就是可迭代对象。

只要一个对象定义了__iter__()方法,那么它就是可迭代对象。

2.2 迭代器

迭代器是对可迭代对象的改造升级,上面说过,一个对象定义了__iter__()方法,那么它就是可迭代对象,进一步地,如果一个对象同时实现了__iter__()和__next()__()方法,那么它就是迭代器。

来,跟我读三遍:

如果一个对象同时实现了__iter__()和__next()__()方法,那么它就是迭代器。

如果一个对象同时实现了__iter__()和__next()__()方法,那么它就是迭代器。

如果一个对象同时实现了__iter__()和__next()__()方法,那么它就是迭代器。

在Python中,也有一个类与迭代器对应:Iterator。所以,要判断一个类是否是迭代器,只要判断是否是Iterator类的实例即可。

from collections.abc importIterablefrom collections.abc importIteratorclassB():def __iter__(self):pass

def __next__(self):pass

print('B()是可迭代对象吗:',isinstance(B(), Iterable))print('B()是迭代器吗:',isinstance(B(), Iterator))

结果输出如下:

B()是可迭代对象吗: True

B()是迭代器吗: True

可见,迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。

所以整型、布尔一定不是迭代器,因为他们连可迭代对象都算不上。那么,字符串、列表、字典、元组是迭代器吗?猜猜!

>>> from collections.abc importIterator>>> isinstance('abc', Iterator)

False>>>isinstance([], Iterator)

False>>>isinstance({}, Iterator)

False>>>isinstance((), Iterator)

False

惊不惊喜,意不意外,字符串、列表、字典、元组都不是迭代器。那为什么它们可以在for循环中遍历呢?而且,我想,看到这里,就算你已经可以在形式上区分可迭代对象和迭代器,但是你可能会问,这有什么卵用吗?确实,没多少卵用,因为我们还不知道__iter__()、__next__()到底是个什么鬼东西。

接下来,我们通过继续探究for循环的本质来解答这些问题。

2.3 for循环的本质

说到__iter__()和__next__()方法,就很有必要介绍一下iter()和next()方法了。

(1)iter()与__iter__()

__iter__()的作用是返回一个迭代器,虽然上面说过,只要实现了__iter__()方法就是可迭代对象,但是,没有实现功能(返回迭代器)总归是有问题的,就像一个村长,当选之后,那就是村长了,但是如果尸位素餐不做事,那总是有问题的。

__iter__()方法毕竟是一个特殊方法,不适合直接调用,所以Python提供了iter()方法。iter()是Python提供的一个内置方法,可以不用导入,直接调用即可。

from collections.abc importIteratorclassA():def __iter__(self):print('A类的__iter__()方法被调用')returnB()classB():def __iter__(self):print('B类的__iter__()方法被调用')returnselfdef __next__(self):passa=A()print('对A类对象调用iter()方法前,a是迭代器吗:', isinstance(a, Iterator))

a1=iter(a)print('对A类对象调用iter()方法后,a1是迭代器吗:', isinstance(a1, Iterator))

b=B()print('对B类对象调用iter()方法前,b是迭代器吗:', isinstance(b, Iterator))

b1=iter(b)print('对B类对象调用iter()方法后,b1是迭代器吗:', isinstance(b1, Iterator))

运行结果如下:

对A类对象调用iter()方法前,a是迭代器吗: False

A类的__iter__()方法被调用

对A类对象调用iter()方法后,a1是迭代器吗: True

对B类对象调用iter()方法前,b是迭代器吗: True

B类的__iter__()方法被调用

对B类对象调用iter()方法后,b1是迭代器吗: True

对于B类,因为B类本身就是迭代器,所以可以直接返回B类的实例,也就是说self,当然,你要是返回其他迭代器也没毛病。对于类A,它只是一个可迭代对象,__iter__()方法需要返回一个迭代器,所以返回了B类的实例,如果返回的不是一个迭代器,调用iter()方法时就会报以下错误:

TypeError: iter() returned non-iterator of type 'A'

(2)next()与__next__()

__next__()的作用是返回遍历过程中的下一个元素,如果没有下一个元素则主动抛出StopIteration异常。而next()就是Python提供的一个用于调用__next__()方法的内置方法。

下面,我们通过next()方法来遍历一个list:

>>> list_1 = [1, 2, 3]>>>next(list_1)

Traceback (most recent call last):

File"", line 1, in next(list_1)

TypeError:'list' object is notan iterator>>> list_2 =iter(list_1)>>>next(list_2)1

>>>next(list_2)2

>>>next(list_2)3

>>>next(list_2)

Traceback (most recent call last):

File"", line 1, in next(list_2)

StopIteration

因为列表只是可迭代对象,不是迭代器,所以对list_1直接调用next()方法会产生异常。对list_1调用iter()后就可以获得是迭代器的list_2,对list_2每一次调用next()方法都会取出一个元素,当没有下一个元素时继续调用next()就抛出了StopIteration异常。

>>> classA():def __init__(self, lst):

self.lst=lstdef __iter__(self):print('A.__iter__()方法被调用')returnB(self.lst)>>> classB():def __init__(self, lst):

self.lst=lst

self.index=0def __iter__(self):print('B.__iter__()方法被调用')returnselfdef __next__(self):try:print('B.__next__()方法被调用')

value=self.lst[self.index]

self.index+= 1

returnvalueexceptIndexError:raiseStopIteration()>>> a = A([1, 2, 3])>>> a1 =iter(a)

A.__iter__()方法被调用>>>next(a1)

B.__next__()方法被调用1

>>>next(a1)

B.__next__()方法被调用2

>>>next(a1)

B.__next__()方法被调用3

>>>next(a1)

B.__next__()方法被调用

Traceback (most recent call last):

File"", line 11, in __next__value=self.lst[self.index]

IndexError: list index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):

File"", line 1, in next(a1)

File"", line 15, in __next__

raiseStopIteration()

StopIteration

A类实例化出来的实例a只是可迭代对象,不是迭代器,调用iter()方法后,返回了一个B类的实例a1,每次对a1调用next()方法,都用调用B类的__next__()方法。

接下来,我们用for循环遍历一下A类实例:

>>> for i in A([1, 2, 3]):print('for循环中取出值:',i)

A.__iter__()方法被调用

B.__next__()方法被调用

for循环中取出值:1B.__next__()方法被调用

for循环中取出值:2B.__next__()方法被调用

for循环中取出值:3B.__next__()方法被调用

通过for循环对一个可迭代对象进行迭代时,for循环内部机制会自动通过调用iter()方法执行可迭代对象内部定义的__iter__()方法来获取一个迭代器,然后一次又一次得迭代过程中通过调用next()方法执行迭代器内部定义的__next__()方法获取下一个元素,当没有下一个元素时,for循环自动捕获并处理StopIteration异常。如果你还没明白,请看下面用while循环实现for循环功能,整个过程、原理都是一样的:

>>> a = A([1, 2, 3])>>> a1 =iter(a)

A.__iter__()方法被调用>>> whileTrue:try:

i=next(a1)print('for循环中取出值:', i)exceptStopIteration:breakB.__next__()方法被调用

for循环中取出值:1B.__next__()方法被调用

for循环中取出值:2B.__next__()方法被调用

for循环中取出值:3B.__next__()方法被调用

作为一个迭代器,B类对象也可以通过for循环来迭代:>>> for i in B([1, 2, 3]):print('for循环中取出值:',i)

B.__iter__()方法被调用

B.__next__()方法被调用

for循环中取出值:1B.__next__()方法被调用

for循环中取出值:2B.__next__()方法被调用

for循环中取出值:3B.__next__()方法被调用

看出来了吗?这就是for循环的本质。

3 生成器

3.1 迭代器与生成器

如果一个函数体内部使用yield关键字,这个函数就称为生成器函数,生成器函数调用时产生的对象就是生成器。生成器是一个特殊的迭代器,在调用该生成器函数时,Python会自动在其内部添加__iter__()方法和__next__()方法。把生成器传给 next() 函数时, 生成器函数会向前继续执行, 执行到函数定义体中的下一个 yield 语句时, 返回产出的值, 并在函数定义体的当前位置暂停, 下一次通过next()方法执行生成器时,又从上一次暂停位置继续向下……,最终, 函数内的所有yield都执行完,如果继续通过yield调用生成器, 则会抛出StopIteration 异常——这一点与迭代器协议一致。

>>> from collections.abc importIterable>>> from collections.abc importIterator>>> defgen():print('第1次执行')yield 1

print('第2次执行')yield 2

print('第3次执行')yield 3

>>> g =gen()>>>isinstance(g, Iterable)

True>>>isinstance(g, Iterator)

True>>>g

>>>next(g)

第1次执行1

>>>next(g)

第2次执行2

>>>next(g)

第3次执行3

>>>next(g)

Traceback (most recent call last):

File"", line 1, in next(g)

StopIteration

可以看到,生成器的执行机制与迭代器是极其相似的,生成器本就是迭代器,只不过,有些特殊。那么,生成器特殊在哪呢?或者说,有了迭代器,为什么还要用生成器?

从上面的介绍和代码中可以看出,生成器采用的是一种惰性计算机制,一次调用也只会产生一个值,它不会将所有的值一次性返回给你,你需要一个那就调用一次next()方法取一个值,这样做的好处是如果元素有很多(数以亿计甚至更多),如果用列表一次性返回所有元素,那么会消耗很大内存,如果我们只是想要对所有元素依次一个一个取出来处理,那么,使用生成器就正好,一次返回一个,并不会占用太大内存。

举个例子,假设我们现在要取1亿以内的所有偶数,如果用列表来实现,代码如下:

deffun_list():

index= 1temp_list=[]while index < 100000000:if index % 2 ==0:

temp_list.append(index)print(index)

index+= 1

return temp_list

上面程序会先获取所有符合要求的偶数,然后一次性返回。如果你运行了代码,你就会发现两个问题——运行时间很长、消耗很多内存。

有时候,我们并不一定需要一次性获得所有的对象,需要一个使用一个就可以,这样的话,可以用生成器来实现:

>>> deffun_gen():

index= 1

while index < 100000000:if index % 2 ==0:yieldindex

index+= 1

>>>fun_gen()

>>> g =fun_gen()>>>next(g)2

>>>next(g)4

>>>next(g)6

看到了吗?对生成器没执行一次next()方法,就会返回一个元素,这样的话无论在速度上还是机器性能消耗上都会好很多。如果你还没感受到生成器的优势,我再说一个应用场景,假如需要取出远程数据库中的100万条记录进行处理,如果一次性获取所有记录,网络带宽、内存都会有很大消耗,但是如果使用生成器,就可以取一条,就在本地处理一条。

不过,生成器也有不足,正因为采用了惰性计算,你不会知道下一个元素是什么,更不会知道后面还有多少元素,所以,对于列表、元组等结构,我们能调用len()方法获知长度,但是对于生成器却不能。

总结一下迭代器与生成器的异同:

(1)生成器是一种特殊的迭代器,拥有迭代器的所有特性;

(2)迭代器使用return返回值而生成器使用yield返回值每一次对生成器执行next()都会在yield处暂停;

(3)迭代器和生成器虽然都执行next()方法时返回下一个元素,迭代器在实例化前就已知所有元素,但是采用惰性计算机制,共有多少元素,下一个元素是什么都是未知的,每一次对生成器对象执行next()方法才会产生下一个元素。

3.2 生成器解析式

使用过列表解析式吗?语法格式为:[返回值 for 元素 in 可迭代对象 if 条件]

看下面代码:

>>> li =[]>>> for i in range(5):if i%2==0:

li.append(i**2)>>>li

[0,4, 16]

我们可以用列表解析式实现同样功能:

>>> li = [i**2 for i in range(5) if i%2==0]>>>li

[0,4, 16]>>>type(li)

很简单对不对?简洁了很多,返回的li就是一个列表。咳咳……偏题了,我们要说的是生成器解析式,而且我相信打开我这篇博文的同学大多都熟悉列表解析式,回归正题。

生成器解析式语法格式为:(返回值 for 元素 in 可迭代对象 if 条件)

你没看错,跟列表解析式相比,生成器解析式只是把方括号换成了原括号。来感受一下:

>>> g = (i**2 for i in range(5) if i%2==0)>>>g at 0x00000222DC2F4468>

>>>next(g)

0>>>next(g)4

>>>next(g)16

>>>next(g)

Traceback (most recent call last):

File"", line 1, in next(g)

StopIteration

可以看到,生成器解析式返回的就是一个生成器对象,换句话说生成器解析式是生成器的一种定义方式,这种方式简单快捷,当然实现的功能不能太复杂。

4 总结

本文全面总结了Python中可迭代对象、迭代器、生成器知识,我相信,只要你认真消化我这篇博文,就能深刻领悟迭代器生成器。

pythonfor循环遍历list_为什么for循环可以遍历list:Python中迭代器与生成器相关推荐

  1. matlab循环数组里的数据库,用于在matlab的python中循环数组

    Matlab代码的直译应该是import numpy as np x = np.zeros((parts, 2)) for i in range(parts): x[i,0] = i*L + 1 x[ ...

  2. c++ 遍历list_数据结构之图的遍历,一篇文章get全部考点

    图的概念 举个例子,假设你的微信朋友圈中有若干好友:张三.李四.王五.赵六.七大姑.八大姨. 而你七大姑的微信号里,又有若干好友:你.八大姨.Jack.Rose. 微信中,许许多多的用户组成了一个多对 ...

  3. python中for循环流程图_Python中的迭代遍历 for in

    遍历就是从头到尾依次从列表中获取数据,在循环体内部针对每一个元素,执行相同的操作. 在Python中为了提高列表的遍历效率,专门提供的迭代(iteration)遍历. 使用 for...in 就能够在 ...

  4. Python中带else子句的for循环执行过程

    这几天在厦门讲课,每天6小时,没有太多时间写新代码,宾馆不知道咋想的也不提供WiFi,只好用手机做个热点临时分享一点基础知识,300M的包月流量伤不起,热点瞬间就把仅剩的40M流量用完了,赶紧发完关闭 ...

  5. [转载] python中for语句用法_详解Python中for循环的使用_python

    参考链接: 在Python中将else条件语句与for循环一起使用 这篇文章主要介绍了Python中for循环的使用,来自于IBM官方网站技术文档,需要的朋友可以参考下 for 循环 本系列前面 &q ...

  6. Python中的迭代遍历 for in

    遍历就是从头到尾依次从列表中获取数据,在循环体内部针对每一个元素,执行相同的操作. 在Python中为了提高列表的遍历效率,专门提供的迭代(iteration)遍历. 使用for...in就能够在 P ...

  7. python中遍历结构可以是哪些数据类型_全!Python基础之原生数据类型、判断和循环、函数和文件操作合集...

    长文预警! Python基础系列会将基础内容大致分为三到五个板块,每块着重讲一方面,知识不会很难,主要是以小例子的形式解读,如果你已经入门Python,希望可以帮你温习一下:如果你想入门Python, ...

  8. python无限循环和遍历循环,python中什么叫遍历

    python循环语句是什么? python 循环语句:while 判断条件(condition):执行语句(statements)--执行语句可以是单个语句或语句块. 判断条件可以是任何表达式,任何非 ...

  9. pythonfor循环语句1到7、不要4_【IT专家】关于Python中的for循环控制语句

    本文由我司收集整编,推荐下载,如有疑问,请与我司联系 关于 Python 中的 for 循环控制语句 2010/06/14 500461 #第一个:求 50 - 100 之间的质数 import ma ...

最新文章

  1. 基于HTML5实现的(本地存储)多标签页面元素的复制粘贴
  2. CentOS No manual entry for man 没有 xx 的手册页条目
  3. 未解决:错误的结果 2 (从“D:\Program Files\Microsoft Visual Studio 8\VC\bin\cl.exe”返回)。...
  4. Oracle-知识结构漫谈
  5. html内容显示重叠了,HTML:将DIV内容并排放置而不重叠
  6. powerdesigner 生成数据库脚本
  7. Servlet+MySQL使用DBCP数据库连接池实现用户登录
  8. 提交git push 的时候报错,Please make sure you have the correct access rights
  9. HTML Email 编写指南
  10. 货币市场基金的基本分类
  11. VS下如何配置才能使用 cl 命令行方式编译 C/C++ 程序
  12. iOS上架被拒理由及相关解决方案记录
  13. ORC 和 Parquet比较入门
  14. 一文看懂BGP路由黑洞问题
  15. 41.MySQL 主从复制, 双主热备
  16. ASP.NET对IIS中的虚拟目录进行操作
  17. iOS开发——源代码管理——git(分布式版本控制和集中式版本控制对比,git和SVN对比,git常用指令,搭建GitHub远程仓库,搭建oschina远程仓库 )...
  18. Go 高性能编程心法探秘
  19. Office噩梦公式远程代码执行漏洞
  20. 更改计算机一级密码,详细教您如何更改win10系统电脑密码

热门文章

  1. java8 lambda 表达式详解
  2. 深入理解Java线程池:ThreadPoolExecutor
  3. SQL Server孤立账户解决办法
  4. 无参数的lambda匿名函数
  5. Kafka集群配置说明
  6. 腾讯云无法绑定公网IP问题解释与解决方案。
  7. 对数据库表中的某一字段去重分组排序
  8. CentOS6.5 下sciki-learn numpy scipy 的安装
  9. 得到的旋转向量和平移向量转换成旋转矩阵 (SE(3))
  10. SLAM之特征匹配(一)————RANSAC-------OpenCV中findFundamentalMat函数使用的模型