pythonfor循环遍历list_为什么for循环可以遍历list:Python中迭代器与生成器
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中可迭代对象、迭代器、生成器知识,我相信,只要你认真消化我这篇博文,就能深刻领悟迭代器生成器。
相关文章:

Linux下查看和添加环境变量
转自:http://blog.sina.com.cn/s/blog_688077cf01013qrk.html $PATH:决定了shell将到哪些目录中寻找命令或程序,PATH的值是一系列目录,当您运行一个程序时,Linux在这些目录下进行搜寻编译链接。 编辑你的 PATH 声明&am…

iis7下站点日志默认位置
iis7下站点日志默认位置 原文:iis7下站点日志默认位置iis7下站点日志默认位置在iis6时,通过iis管理器的日志配置可以找到站点日志存储的位置。但是在iis7下,iis管理器下的日志配置只能找到iis日志配置的主目录,但到底在哪个子目录,…

go语言有哪些优势
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 1、学习曲线容易 Go语言语法简单,包含了类C语法。因为Go语言容易学习,所以一个普通的大学生花几个星期就能写出来可以上手的…

重定向后,如何通过浏览器返回定向之前的页面?
js实现页面跳转重定向的几种方式 第一种: 代码如下: <script language"javascript"type"text/javascript">window.location.href"http://shanghepinpai.com";</script> 第二种: 代码如下: <script languag…

金蝶中间件部署报栈溢出_京东618压测时自研中间件暴露出的问题,压测级别数十万/秒...
618大促演练进行了全链路压测,在此之前刚好我的热key探测框架也已经上线灰度一周了,小范围上线了几千台服务器,每秒大概接收几千个key探测,每天大概几亿左右,因为量很小,所以框架表现稳定。借着这次压测&am…

利用box-shadow绘图
上篇博客提到过,box-shadow属性的本质是对形状的复制,那么如果我设置一个1*1px的i标签,利用box-shadow可以叠加的特性,给每一个1*1px的阴影赋上颜色,那么最后不就是一幅图片了么。 html代码很简单: <!do…

为什么要使用Go语言?Go语言的优势在哪里?
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 Go语言之所有越来越受到开发者的欢迎,我认为与其超高的实用价值密不可分。要知道Go语言是为了解决现实问题而设计的,而不是为…

BI之SSAS完整实战教程3 -- 创建第一个多维数据集
上一篇我们已经完成了数据源的准备工作,现在我们就开始动手,创建第一个多维数据集(Cube)。 文章提纲 使用多维数据集向导创建多维数据集 总结Cube设计器简介 维度细化 总结 一、使用向导创建多维数据集 在Analysis Services中,可以通过3种…

python opencv local_threshold_Python-OpenCV中的cv2.threshold
主要记录Python-OpenCV中的cv2,threshold()方法;官方文档 cv2.threshold() def threshold(src, thresh, maxval, type, dstNone): """ 设置固定级别的阈值应用于多通道矩阵 例如,将灰度图像变换二值图像,或去除指定级别的噪声…

java中decimalFormat格式化数值
介绍 我们经常要对数字进行格式化,比如取小数点后两位小数,或者加个百分比符号等,Java提供了DecimalFormat这个类0 和 # 的区别 "#"可以理解为在正常的数字显示中,如果前缀与后缀出现不必要的多余的0,则将其…

GO语言有哪些优势?怎样入门?
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 1、学习曲线 它包含了类C语法、GC内置和工程工具。这一点非常重要,因为Go语言容易学习,所以一个普通的大学生花一个星期就能…

POJ-2955 Brackets
题目大意: 给你一个只由(、)、[、]组成的字符串,问你这个字符串的子串能够匹配的最长长度是多少。 能够匹配的意思是这样的: 1.如果s是个空串,那么它是匹配的。 2.如果子串是(s)或者[s],那么它也是匹配的,其…

CentOS7.4-btrfs管理及使用
btrfs, B-tree File System, GPL开源文件系统, 支持CoW即读时写入. 核心特性: 多物理卷支持;btrfs可由多个底层磁盘组成支持RAID mkfs.btrfs 命令的man文档支持: raid0, raid1, raid5, raid6,raid10, single or dup联机"添加, 移除, 修改" CoW写时复制更新机制 对文件…

取消对 null 指针“l”的引用。_C++中的引用
当变量声明为引用时,它将成为现有变量的替代名称。通过在声明中添加“&”,可以将变量声明为引用。#include using namespace std; int main() { int x 10; // ref is a reference to x. int& ref x; // Value of x is no…

你没听说过的Go语言惊人优点
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 在这篇文章中,我将讨论为什么你需要尝试一下 Go 语言,以及应该从哪里学起。 Go 语言是可能是最近几年里你经常听人说起的编…

JS document
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><div id"one">今天下雨</div><table border"1" cellspacing"0" cellpadding"…

python 流写入文件_python文件流操作
博主在学习python时对文件进行操作时经常踩一下坑。所以专门梳理了一下。有问题麻烦指出哈。 python对于文件的操作我们一般是用open()。我们根据python的源码可以看出。我们必须要传的参是file即打开文件的URL。同时open方法默认是是r的打开方式即只读。…

使用 Python 从零开始开发区块链应用程序
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 “区块链”是什么? 区块链是一种存储数字数据的方式。数据可以是任何内容。对于比特币,它是事务(在帐户之间转移…

Mybatis学习记录-使用问题总结之一DISTINCT
问题1:手动修改的查询语句,放入到项目中后显示结果和实际查询结果不一致 由于实际情况中用的了分页功能,导致最终的语句在查询完成后,添加了分页项,即如下代码。 ROW_NUMBER() OVER ( ORDER BY COLUMNS) PAGE_ROW_NUMB…

python xlrd读取excel所有数据_python读取excel进行遍历/xlrd模块操作
我就废话不多说了,大家还是直接看代码吧~ #!/usr/bin/env python # -*- coding: utf-8 -*- import csv import xlrd import xlwt def handler_excel(filenamer/Users/zongyang.yu/horizon/ops_platform/assets/upload/1.xlsl): # 打开文件 workbook xlrd.open_work…

【NOIP2015提高组Day1】 神奇的幻方
【问题描述】 幻方是一种很神奇的 N*N矩阵:它由数字1,2,3, … … ,N*N 构成,且每行、每列及两条对角线上的数字之和都相同。 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在第一行的中间。 之后,按如下…

40行python开发一个区块链
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 尽管有人认为区块链目前还是个不成熟的解决方案,但它无疑称得上是计算机发展历史上的一个奇迹。但是,到底区块链是什么呢?…

网络实验的背景流
在最近做的网络实验中,发现背景流必须要先于实验流开始,并且要长于实验流的时间,这样才能看出实验流的规律。如果背景流后发于实验流,就会变成竞争模式,实验流就会被抢占或者挤压。转载于:https://www.cnblogs.com/fen…

python捕获异常后处理_python异常捕获处理
一、异常处理 在程序运行过程中,总会遇到各种各样的错误。程序一旦出错就停止运行了,此时就需要捕捉异常,通过捕捉到的异常,我们再去做对应的处理 写一个函数,实现除法运算 def calc(a,b): return a/b print(calc(5,1)…

《JS权威指南学习总结--第十一章子集和扩展》
js子集和扩展:http://www.cnblogs.com/ahthw/p/4298449.html ES6新增let和const关键字:http://www.cnblogs.com/telnetzhang/p/5639949.html JS中 var 和 let 关键字的区别:http://www.w3cfuns.com/notes/21400/891cac0f6bff2d7f25d3084618e8…

最常见的 35 个 Python 面试题及答案
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 作为一个 Python 新手,你必须熟悉基础知识。在本文中我们将讨论一些 Python 面试的基础问题和高级问题以及答案,以帮助你完…

PHP 中日期时间函数 date() 用法总结
[导读] date()是我们常用的一个日期时间函数,下面我来总结一下关于date()函数的各种形式的用法,有需要学习的朋友可参考。格式化日期date() 函数的第一个参数规定了如何格式化日期 时间。它使用字母来表示日期和时间 格式化日期date() 函数的第一个参数规…

mac mysql的安装
mac是重装的系统,很干净,没有xmpp等组合的服务器。 1. 安装mysql server https://dev.mysql.com/downloads/mysql/ 这里是官网地址,选择需要的版本下载,我下载的是第一个dmg的,进入后,会让登陆或注册&#…

windows系统和linux系统可以使用相同的js代码吗_「React 手册 」在 Windows 下使用 React , 你需要注意这些问题...
大家好,本篇内容,我要和大家聊聊使用 Windows 开发 React ,你需要注意的一些问题。首先说明下,我不是使用 windows 进行开发,因为其配置开发环境来说不是特别方便,我更喜欢 苹果mac 或者乌班图这样的系统&a…

以太坊:比特币 + 无限可能
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 还记你得刚学编程时,第一次使用“对象”的感觉吗?还记你第一次尝试函数式编程的样子吗?这些编程范式࿰…