Python中有一类工具叫做迭代工具,它能从左至右扫描对象。这包括了for循环、列表解析、in成员关系测试以及map内置函数等。可以用在上述迭代工具环境中,通过一次次迭代不断产生结果的对象称为可迭代对象,即是Iterable。
实际上可迭代对象分为两大类,一种是实际保存的序列,即列表、元组,字符串;另一种就是 “不一次性产生所有结果列表,而是可以在for循环中按需一次产生一个结果的对象”。如:range函数返回值、zip函数返回值、enumerate函数返回值等等,与实际序列相对应,这个叫做按需产生对象的虚拟序列。
而迭代器是一个含有 next() 方法的对象,可以被next()函数不断调用并返回下一个值(迭代器从对象的第一个元素开始访问),直到所有的元素被遍历后结束,超出范围后能够抛出StopIteration的错误停止迭代。
迭代器可以使你迭代不是序列而表现出序列行为的对象,如字典的键、一个文件的行等。当你使用循环迭代一个对象条目时,几乎分不出它是迭代器还是序列,也不必去关心这些,python可以让它像一个序列那样操作。
>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
对象里面包含__iter()__方法的实现,但它没有next方法,不是一个迭代器。
>>> from collections import Iterator
>>> isinstance([],Iterator)
False
>>> isinstance({},Iterator)
False
>>> isinstance(str,Iterator)
False
list、dict、str虽然是Iterable,却不是Iterator,把list、dict、str等Iterable变成Iterator需要调用对象中的iter方法,调用成功后会返回一个迭代器,里面包含具体数据获取的实现。迭代器为类序列对象提供了一个类序列的接口(只要是实现了 __iter__() 方法的对象,就可以使用迭代器来进行访问)。
>>> isinstance(iter([]),Iterator)
True
>>>
iter()函数实现将一个可迭代对象转换为迭代器,然后调用next()方法就可以按需取出元素。
>>> alist=[1,2,4,5,'a']
>>> iter_list=iter(alist) # Invokes alist.__iter__()
>>> next(iter_list) # Invokes iter_list.__next__()
1
>>> next(iter_list)
2
>>>
一般我们会使用for循环语句用来遍历一个可迭代对象,因此迭代器也可以作用于for循环语句,这样可以将迭代器中元素依次取出
>>> alist=[1,2,4,5,'a']
>>> for i in alist:
... print(i,end=' ')
...
1 2 4 5 a
>>>
>>>iter_list= iter(alist) # Invokes alist.__iter__()
>>> for i in iter_list:
... print(i,end=' ')
...
1 2 4 5 a
>>> next(iter_list) #迭代完毕,抛出异常
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
next(iter_list)
StopIteration
>>>
python的for循环机制本质上就是需要下一项时通过不断调用next()函数实现的,实际上完全等价于:
# 首先获得Iterator对象:
it = iter(alist) # Invokes alist.__iter__()
# 循环:
while True:
try:
# 依次获得下一个值:
x = next(it) # Run the iterator, Invokes it.__next__()
except StopIteration:
# 遇到StopIteration就退出循环
break
for这个语法背后的是首先获取可迭代对象返回的迭代器对象,然后不断调用迭代器对象的next方法获取每个值,在获取值的过程中随时检测边界-也就是检查是否抛出了StopIteration这样的异常,如果迭代器对象抛出错误则迭代停止,异常只是告诉外部调用者,迭代完成。
或者当任何可迭代对象传入到for循环或其他迭代工具(如list)中进行遍历时,迭代工具都是先通过iter函数获得与可迭代对象对应的迭代器,然后再对迭代器调用next函数,不断的依次获取元素,并在捕捉到StopIteration异常时确定完成迭代,这就是完整的迭代过程。这也称之为“迭代协议”。
使用迭代器的理由
首先看那么为什么list、dict、str等数据类型不是Iterator?
这是因为python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
它与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存中,而是一种延时计算方式返回元素,如列表中含有一亿个数据(假如整型占4个字节),需要内存400M,而迭代器只需要几十个字节的空间,这是因为它没有把所有元素加载到内存中,而是等待调用next方法才返回该元素(按需调用call by need的方式)。
1、“流式”数据处理方式减少内存消耗:
比如处理文件,一下猛地把全部数据全部取出来放到内存里面进行处理会导致程序消耗大量内存,有时甚至没法做到,一般我们会一部分一部分的对文件内容进行处理:
for text_line in open("xx.txt"):
print text_line
open("xx.txt")返回的文件对象是迭代器,所以可以渐进式地对文件的内容进行处理,即按行来读取文件(自动调用readline()方法),并进行处理,而不是直接把全部文件一下加载到内存中。
如下读取一个大文件,要求输出相同时间戳内状态为200的平均连接数
#序号 时间戳 连接数 转态码
[root@localhost newtest]# cat cookies.txt
1 12345 12 200
2 12345 11 100
3 12345 19 200
4 12346 11 200
5 12346 19 200
6 12347 11 200
7 12348 10 200
8 12348 11 100
9 12348 11 200
10 12348 12 200
统计脚本
def linknumpersecond(filename):
fp=open(filename,'r')
fronttimestamp=0 #初始状态
persecondlinknum=0
cnt=0
for line in fp:
linelist = line.split(' ') #当前处理结果
status = linelist[3].strip('\n')
timestamp = linelist[1]
linknum=int(linelist[2])
if timestamp==fronttimestamp and status=='200':
persecondlinknum+=linknum
cnt+=1
else:
if timestamp !=fronttimestamp: #如果和上一次不一样,就输出上一次的统计结果
print(fronttimestamp,persecondlinknum,cnt)
if status=='200':
persecondlinknum=linknum #重新计算新的时间戳对应的连接数
fronttimestamp=timestamp
print(fronttimestamp,persecondlinknum,cnt) #最后的时间戳
测试用例
>>> linknumpersecond('/newtest/cookies.txt')
(0, 0, 0)
('12345', 31, 1)
('12346', 30, 2)
('12347', 11, 2)
('12348', 33, 4)
>>>
2、支持方便用for语句对数据进行处理
python内置的一些常见的类型像数组、列表甚至字符串等都是可迭代类型,这样我们就能方便for语句这个语法方便对数据进行消费,不需要自己记录索引位置。
1、 在字典和列表中迭代会带来性能上的提升
2、 迭代非序列集合(如映射、文件)时,代码更简洁可读