【Python学习手册(第四版)】学习笔记14-迭代器和列表解析(一)

个人总结难免疏漏,请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。

本文主要以通俗易懂的语言介绍迭代器(文件迭代、手动迭代iter和next等)列表解析式包括基础知识包括写法、文件上使用列表解析、扩展列表解析语法等对列表解析不懂的同学着重推荐本。最后介绍了Python3.0后的新可迭代对象,range、map、zip、filter迭代器,以及多个、单个迭代器比较,还有字典视图迭代器。

本文较长视个人情况决定选择目录阅读,难度很低,无基础同学可能读的稍慢。有基础的同学理解很快,没有用复杂的语言表述,没有浓缩含义。


目录

前言

迭代器:初探

文件迭代器

手动迭代:iter和next

其他内置类型迭代器

列表解析式:初探

列表解析基础知识

在文件上使用列表解析

扩展列表解析语法

其他迭代环境

Python3.0后的新的可迭代对象

range迭代器

map、zip和filter迭代器

多个迭代器VS单个迭代器

字典视图迭代器

其他迭代器主题


前言

本文所用编辑器为Python IDLE 3.12.4,与之前IDLE 3.9不同的是:复制出来的代码行前没有>>>,但是输出结果不缩进,且Windows可视化界面不影响。对于csdn代码展示的不便观看的将空一行。

for line in open('script1.py'):
    print(line)

x = 2

print(2 ** 33)

迭代器:初探

Python的两种循环语句,while和for,它们能够处理程序所需执行的大多数重复性任务,在序列中迭代的需求是如此常见和广泛,以至Python提供了额外的工具以使其更简单和高效。

概念乍看起来可能有些高级。但是,通过实践,你将发现这些工具很有用并且很强大。尽管这些工具不是严格必需的,但已经变成了Python代码中的常用内容,如果你必须阅读他人所编写的程序,那么就应基本理解这些工具。

前面的学习笔记已经见到了,for循环可以用于Python中任何序列类型,包括列表、元组以及字符串,如下所示:

实际上,for循环甚至比这更为通用:可用于任何可迭代的对象。

对Python中所有会从左至右扫描对象的迭代工具而言都是如此,这些迭代工具包括了for循环、列表解析、in成员关系测试等。

“可迭代对象”的概念在Python中的语言设计中很普遍。基本上,这就是序列观念的通用化:如果对象是实际保存的序列,或者可以在迭代工具环境中(例如,for循环)一次产生一个结果的对象,就看做是可迭代的。总之,可迭代对象包括实际序列和按照需求而计算的虚拟序列。

文件迭代器

了解迭代器含义的最简单的方式之一就是,看一看它是如何与内置类型一起工作的,例如,文件。

在 学习笔记9 中,已打开的文件对象有个方法名为readline,可以一次从一个文件中读取一行文本,每次调用readline方法时,就会前进到下一列。到达文件末尾时,就会返回空字符串,可通过它来检测,从而跳出循环。

f = open('script1.py')
        
f.readline()
        
'x = 2\n'
f.readline()
        
'print(2 ** 33)\n'
f.readline()
        
''

文件也有一个方法,名为__next__(双下划线),差不多有相同的效果:每次调用时,就会返回文件中的下一行。唯一值得注意的区别在于,到达文件末尾时,__next__会引发内置的StopIteration异常,而不是返回空字符串。

f = open('script1.py')

f.__next__()
'x = 2\n'

f.__next__()
'print(2 ** 33)\n'

f.__next__()
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    f.__next__()
StopIteration

这个接口就是Python中所谓的迭代协议:有__next__方法的对象会前进到下一个结果,而在一系列结果的末尾时,则会引发StopIteration。

在Python中,任何这类对象都认为是可迭代的。任何这类对象也能以for循环或其他迭代工具遍历,因为所有迭代工具内部工作起来都是在每次迭代中调用__next__,并且捕捉StopIteration异常来确定何时离开。

就像之前所提到过的,这种效果就是,逐行读取文本文件的最佳方式就是根本不要去读取;其替代的办法就是,让for循环在每轮自动调用next从而前进到下一行。

例如,下面是逐行读取文件(程序执行时打印每行的大写版本),没有刻意从文件中读取内:

for line in open('script1.py'):
    print(line.upper())

    
X = 2

PRINT(2 ** 33)

上例是读取文本文件的最佳方式,原因有三:这是最简单的写法,运行最快,并且从内存使用情况来说也是最好的

相同效果的原始方式,是以for循环调用文件的readlines方法,将文件内容加载到内存,做成行字符串的列表。

for line in open('script1.py').readlines():
    print(line.upper())

    
X = 2

PRINT(2 ** 33)

readlines技术依然能用,从内存的使用情况来看,效果很差。因为这个版本其实是一次把整个文件加载到内存,如果文件太大,以至于计算机内存空间不够,甚至不能够工作。一次读一行,迭代器版本对这类内存爆炸的问题就有了免疫能力。

当然也可以用while循环逐行读取文件。

f = open('script1.py')
while True:
    line = f.readline()
    if not line: break
    print(line.upper())

    
X = 2

PRINT(2 ** 33)

尽管比起迭代器for循环的版本,这可能运行得更慢一些,因为迭代器在Python中是以C语言的速度运行的,而while循环版本则是通过Python虚拟机运行Python字节码的。

任何时候,我们把Python代码换成C程序代码,速度都应该会变快。但是并非绝对如此。

手动迭代:iter和next

为了支持手动迭代代码,有一个内置函数next,它会自动调用一个对象的__next__方法。给定一个可迭代对象X,调用next(X)等同于X.__next__(),但前者简单很多。

例如,对于文件的任何一种形式都可以使用:

f = open('script1.py')
f.__next__()
'x = 2\n'
f.__next__()
'print(2 ** 33)\n'

f = open('script1.py')
next(f)
'x = 2\n'
next(f)
'print(2 ** 33)\n'

迭代协议还有一点值得注意。当for循环开始时,会通过它传给iter内置函数,以便从可迭代对象中获得一个迭代器,返回的对象含有需要的next方法。

l = [1,2,3]
i = iter(l)
next(i)
1
next(i)
2
next(i)
3
next(i)
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    next(i)
StopIteration

最初的一步对于文件来说不是必需的,因为文件对象就是自己的迭代器。文件有自己的__next__方法,因此不需要像这样返回一个不同的对象:

f = open('script1.py')
iter(f) is f
True

f.__next__()
'x = 2\n'

列表以及很多其他的内置对象,不是自身的迭代器,因为它们支持多次打开迭代器。对这样的对象,必须调用iter来启动迭代:

l = [1,2,3]
iter(l) is l
False

l.__next__()
Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    l.__next__()
AttributeError: 'list' object has no attribute '__next__'. Did you mean: '__ne__'?
i = iter(l)
i.__next__()
1

next(i)
2

Python迭代工具自动调用这些函数,也可以使用它们来手动地应用迭代协议。

l = [1,2,3]

for x in l:    #自动调用迭代
    print(x ** 2,end=' ')    #获取迭代 调用__next__

1 4 9 

i = iter(l)    #手动迭代
while True:
    try:    #捕获异常
        x = next(i)    #或者__next__
    except StopIteration:    
        break
    print(x ** 2,end=' ')

    
1 4 9 

try语句运行一个动作并且捕获在运行过程中发生的异常。

for循环和其他的迭代环境有时候针对用户定义的类不同地工作,重复地索引一个对象而不是运行迭代协议

其他内置类型迭代器

除了文件以及像列表这样的实际的序列外,其他类型也有其适用的迭代器。

例如,遍历字典键的经典方法是明确地获取其键的列表。

字典有一个迭代器,在迭代环境中,会自动一次返回一个键。

d = {'a':1,'b':2,'c':3}
i = iter(d)
next(i)
'a'
next(i)
'b'
next(i)
'c'
next(i)
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    next(i)
StopIteration

直接的效果是,不再需要调用keys方法来遍历字典键——for循环将使用迭代协议在每次迭代的时候获取一个键

for key in d:
    print(key,d[key])

    
a 1
b 2
c 3

其他的Python对象类型也支持迭代协议,因此,也可以在for循环中使用。例如,shelves(用于Python对象的一个根据键访问的文件系统)和os.popen的结果(读取shell命令的输出的一个工具)也是可迭代的:

import os
p = os.popen('dir')

p.__next__()
' 驱动器 C 中的卷是 Windows-SSD\n'

next(p)
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    next(p)
TypeError: '_wrap_close' object is not an iterator

在Python 3.0中,支持P.__next__()方法,但不支持next(P)内置函数。

这只是手动迭代的一个问题,如果用for循环或者其他的迭代环境(下一小节介绍)来自动迭代这些对象,在任何Python版本中,它们都将返回连续的行。

迭代协议也是必须把某些结果包装到一个list调用中 以一次性看到它们的值的原因。可迭代的对象一次返回一个结果,而不是一个实际的列表

r = range(5)

r
range(0, 5)

i = iter(r)

next(i)
0

list(range(5))
[0, 1, 2, 3, 4]

对迭代器有了理解后,再来看看如何说明上一章所介绍的enumerate工具能够以其方式工作的原因:

e = enumerate('life')

e
<enumerate object at 0x00000206C175AD90>

i = iter(e)

next(i)
(0, 'l')

next(i)
(1, 'i')

list(enumerate('life'))
[(0, 'l'), (1, 'i'), (2, 'f'), (3, 'e')]

通常不会看到这种机制,因为for循环为我们自动遍历结果。

实际上,Python中可以从左向右扫描的所有对象都以同样的方式实现了迭代协议。下节详解

列表解析式:初探

理解了迭代协议是如何工作的,来看一个非常常用的例子。与for循环一起使用,列表解析是最常应用迭代协议的环境之一

在上一章学习了,在遍历一个列表的时候,如何使用range来修改它:

这是有效的,但是正如提到的,它可能不是Python中的优化的“最佳实践”。

如今,列表解析表达式使得早先许多的例子变得过时了。

例如,可以用产生所需的结果列表的一个单个表达式来替代该循环:

l = [1,2,3,4,5]

l = [x+10 for x in l]

l
[11, 12, 13, 14, 15]

直接结果是相同的,但是它需要较少的代码,并且可能会运行的更快。

列表解析并不完全和for循环语句版本相同,因为它产生一个新的列表对象(如果有对最初的列表的多个引用,可能会有关系)。可以看到每次id都不同。

id(l)
2228044245440


l = [x+10 for x in l]

id(l)
2228044327552

l = [x+10 for x in l]

id(l)
2228044403712

l
[31, 32, 33, 34, 35]

但是,对于大多数应用程序来说它足够接近,并且是一种足够常见和方便的方法。

列表解析基础知识

从语法上讲,其语法源自于集合理论表示法中的一个结构,该结构对集合中的每个元素应用一个操作。

但是,要使用这个工具并不一定必须知道集合理论。在Python中,大多数人发现列表解析看上去就像是一个反向的for循环。

为了在语法上进行了解,我们会更详细地剖析前面的例子:

l = [x+10 for x in l]
  1. 列表解析写在一个方括号中,因为它们最终是构建一个新的列表的一种方式
  2. 以我们所组成的一个任意的表达式开始,该表达式使用我们所组成的一个循环变量(x+10)。
  3. 后边跟着是一个for循环头部的部分,它声明了循环变量,以及一个可迭代对象(for x in L)。

要运行该表达式,Python在解释器内部执行一个遍历L的迭代,按照顺序把x赋给每个元素,并且收集对各元素运行左边的表达式的结果。得到的结果列表就是列表解析所表达的内容——包含了x+10的一个新列表,针对L中的每个x。

从技术上讲,列表解析并非真的是必需的,因为总是可以用一个for循环手动地构建一个表达式结果的列表,该for循环像下面这样添加结果:

实际上,这和列表解析所做的事情是相同的。

然而,列表解析编写起来更加精简并且由于构建结果列表的这种代码样式在Python代码中十分常见,因此可以将它们用于多种环境

此外,列表解析比手动的for循环语句运行的更快(往往速度会快一倍),因为它们的迭代在解释器内部是以C语言的速度执行的,而不是以手动Python代码执行的,特别是对于较大的数据集合,这是使用列表解析的一个主要的性能优点。

在文件上使用列表解析

列表解析的另一个常见用例,文件对象有一个readlines方法,它能一次性地把文件载入到行字符串的一个列表中:

f = open('script1.py')
lines = f.readlines()

lines
['x = 2\n', 'print(2 ** 33)\n']

这是有效的,因为结果中的行在末尾都包含了一个换行符号(\n)。

对于很多程序来说,换行符号很讨厌,必须小心避免打印的时候留下双倍的空白,等等。如果可以一次性地去除这些换行符号,就很好。

当开始考虑在一个序列中的每项上执行一个操作时,都可以考虑使用列表解析。

例如,假设变量lines像前面交互模式中的一样,如下的代码通过对列表中的每一行运行字符串rstrip方法,来移除右端的空白(一个line[:-1]分片也有效,但是,只有当我们能够确保所有的行都正确结束的时候,它才有效):

lines = [line.rstrip() for line in lines]

lines
['x = 2', 'print(2 ** 33)']

由于列表解析像for循环语句一样是一个迭代环境,甚至不必提前打开文件。

如果在表达式中打开它,列表解析将自动使用在本章前面所介绍的迭代协议。也就是说,它将会调用文件的next方法,每次从文件读取一行

再次,我们得到了想要的内容,即一行的rstrip结果,对于文件中的每一行:

lines = [line.rstrip() for line in open('script1.py')]
lines
['x = 2', 'print(2 ** 33)']

这个表达式做了很多隐式的工作,但是我们将在此揭秘其大多数工作——Python扫描文件并自动构建了操作结果的一个列表

这也是编写这一操作的一种高效率的方式:因为大多数工作在Python解释器内部完成,这可能比等价的语句要快很多。再次,特别是对于较大的文件,列表解析的速度优势可能很显著

除了其高效性,列表解析的表现力也很强

可以在迭代时在一个文件的行上运行任何的字符串操作。下面是与前面遇到的文件迭代器大写示例对等的列表解析,还有几个其他的示例(这些例子中的第二个中的方法链是有效的,因为字符串方法返回一个新的字符串,可以对该字符串应用其他的字符串方法):

lines = [line.upper() for line in open('script1.py')]
lines
['X = 2\n', 'PRINT(2 ** 33)\n']

lines = [line.rstrip().upper() for line in open('script1.py')]
lines
['X = 2', 'PRINT(2 ** 33)']

lines = [line.split() for line in open('script1.py')]
lines
[['x', '=', '2'], ['print(2', '**', '33)']]


lines = ['x' in line for line in open('script1.py')]
lines
[True, False]

[[('T' in line,line[0])] for line in open('script1.py')]
[[(False, 'x')], [(False, 'p')]]

扩展列表解析语法

实际上,列表解析可以有更高级的应用。作为一个特别有用的扩展,表达式中嵌套的for循环可以有一个相关的if子句,来过滤那些测试不为真的结果项

例如,假设想要重复前面小节的文件扫描示例,但是只收集以字母p开头的那些行(可能每一行的第一个字母是某种类型的动作代码)。向表达式中添加一条if过滤子句来实现:

lines = [line.rstrip() for line in open('script1.py') if line[0] == 'p']
lines
['print(2 ** 33)']

这条if子句检查从文件读取的每一行,看它的第一个字符是否是p;如果不是,从结果列表中省略该行。

这是一个相当大的表达式,但是,如果将它转换为简单的for循环语句等价形式的话,它很容易理解。通常我们总是可以把一个列表解析转换为一条for语句,通过逐步附加并进一步缩进每个后续的部分:

res = []
for line in open('script1.py'):
    if line[0] == 'p':
        res.append(line.rstrip())

 
res
['print(2 ** 33)']

这个for语句等价形式也有效,但是,它占据了4行而不是一行,并且可能运行起来要慢很多

如果需要的话,列表解析可以变得更复杂——例如,它们可能包含嵌套的循环,也可能被编写为一系列的for子句。实际上,它们的完整语法允许任意数目的for子句,每个子句有一个可选的相关的if子句

例如,下面的例子构建了一个x+y连接的列表,把一个字符串中的每个x和另一个字符串中的每个y连接起来。它有效地收集了两个字符串中的字符的排列:

[x + y for x in 'abc' for y in 'xyz']
['ax', 'ay', 'az', 'bx', 'by', 'bz', 'cx', 'cy', 'cz']

理解这个表达式的一种方式是通过缩进其各个部分将它转换为语句的形式。下面是其等价形式,但可能会更慢一些,这是实现相同效果的一种替代方式:

res = []
for x in 'abc':
    for y in 'xyz':
        res.append(x + y)

        
res
['ax', 'ay', 'az', 'bx', 'by', 'bz', 'cx', 'cy', 'cz']

除了这一复杂的层级,列表解析表达式往往可以变为更紧凑的形式。通常,它们会缩进以简化迭代的类型对于更多的相关工作,一条简单的for语句结构可能更容易理解,并且将来也更容易修改

与编程中的通常情况一样,如果某些内容对你来说难以理解,它可能不是一个好主意

后面遇到函数式编程工具的时候再次回顾列表解析;将会看到,当列表解析要对语句进行循环的时候和函数相关联的。

其他迭代环境

用户定义的类也可以实现迭代协议。因此,有时候知道哪些内置工具使用了该协议是很重要的——实现了迭代协议的任何工具,都能够在提供了该工具的任何内置类型或用户定义的类上自动地工作。

到目前已经在for循环语句的背景下介绍了迭代,但是在对象中从左到右扫描的每种工具都使用了迭代协议。

列表解析、in成员关系测试、map内置函数以及像sorted和zip调用这样的内置函数也都使用了迭代协议。

当应用于一个文件时,所有这些使用文件对象的迭代器都自动地按行扫描:

uppers = [line.upper() for line in open('script1.py')]
uppers
['X = 2\n', 'PRINT(2 ** 33)\n']

map(str.upper,open('script1.py'))
<map object at 0x00000206C1CF4E20>

list(map(str.upper,open('script1.py')))
['X = 2\n', 'PRINT(2 ** 33)\n']

'x = 2\n' in open('script1.py')
True

map是一个内置函数,它把一个函数调用应用于传入的可迭代对象中的每一项。map类似于列表解析,但是它更有局限性,因为它需要一个函数而不是一个任意的表达式。在Python 3.0中,它还返回一个可迭代的对象自身,因此,必须将它包含到一个list调用中以迫使其一次性给出所有的值。

Python还包含了各种处理迭代的其他内置函数:sorted排序可迭代对象中的各项,zip组合可迭代对象中的各项,enumerate根据相对位置来配对可迭代对象中的项,filter选择一个函数为真的项,reduce针对可迭代对象中的成对的项运行一个函数。

所有这些都接受一个可迭代的对象,并且在Python 3.0中,zip、enumerate和filter也像map一样返回一个可迭代对象。它们实际运行文件的迭代器会自动地按行扫描,如下所示:

sorted(open('script1.py'))
['print(2 ** 33)\n', 'x = 2\n']


list(zip(open('script1.py'),open('script1.py')))
[('x = 2\n', 'x = 2\n'), ('print(2 ** 33)\n', 'print(2 ** 33)\n')]

list(enumerate(open('script1.py')))
[(0, 'x = 2\n'), (1, 'print(2 ** 33)\n')]

list(filter(bool,open('script1.py')))
['x = 2\n', 'print(2 ** 33)\n']

import functools,operator
functools.reduce(operator.add,open('script1.py'))
'x = 2\nprint(2 ** 33)\n'

所有这些都是迭代工具,但它们有独特的作用。上一章见过zip和enumerate,在其他笔记里讨论函数的时候将会介绍filter和reduce,这里暂不详细介绍。

sorted是应用了迭代协议的一个内置函数,它就像是最初的列表sort方法,但是它返回一个新的排序的列表作为结果并且可以在任何可迭代对象上运行。注意,和map及其他的函数不同,sorted在Python中返回一个真正的列表而不是一个可迭代对象。

其他的内置函数也支持可迭代协议(但坦率地讲,很难用在和文件相关的示例中)。

例如,sum调用计算任何可迭代对象中的总数,如果一个可迭代对象中任何的或所有的项为真的时候,any和all内置函数分别返回True;max和min分别返回一个可迭代对象中最大和最小的项。

和reduce一样,如下示例中的所有工具接受任何可迭代对象作为一个参数,并且使用迭代协议来扫描它,但返回单个的结果:

sum([1,3,5,0])
9

any([1,3,5,0])
True

all([1,3,5,0])
False

max([1,3,5,0])
5

min([1,3,5,0])
0

严格地讲,max和min函数也可以应用于文件——它们自动使用迭代协议来扫描文件,并且分别选择具有最高的和最低的字符串值的行:

max(open('script1.py'))
'x = 2\n'
min(open('script1.py'))
'print(2 ** 33)\n'

在当今的Python中,迭代协议甚至比本文目前所能展示的示例要更普遍——Python的内置工具集中从左到右地扫描一个对象的每项工具,都定义为在主体对象上使用了迭代协议

甚至包含了更高级的工具,例如list和tuple内置函数(从可迭代对象构建了一个新的对象),字符串join方法(将一个子字符串放置到一个可迭代对象中包含的字符串之间),甚至包括序列赋值。总之,所有这些都将在一个打开的文件上工作并且自动一次读取一行:

open('script1.py','w').write('x = 2\nprint(2 ** 33)\nddccccc\nccc\nrr111')
38

list(open('script1.py'))
['x = 2\n', 'print(2 ** 33)\n', 'ddccccc\n', 'ccc\n', 'rr111']

tuple(open('script1.py'))
('x = 2\n', 'print(2 ** 33)\n', 'ddccccc\n', 'ccc\n', 'rr111')

'&&'.join(open('script1.py'))
'x = 2\n&&print(2 ** 33)\n&&ddccccc\n&&ccc\n&&rr111'

a, b, c, d, e = open('script1.py')
a,e
('x = 2\n', 'rr111')

a,*b,c = open('script1.py')
a,b,c
('x = 2\n', ['print(2 ** 33)\n', 'ddccccc\n', 'ccc\n'], 'rr111')

之前遇到过内置的dict调用接受一个可迭代的zip结果。看看set调用以及集合解析和字典解析表达式:

set(open('script1.py'))
{'rr111', 'x = 2\n', 'print(2 ** 33)\n', 'ccc\n', 'ddccccc\n'}

{line for line in open('script1.py')}
{'rr111', 'x = 2\n', 'print(2 ** 33)\n', 'ccc\n', 'ddccccc\n'}

{x: line for x,line in enumerate(open('script1.py'))}
{0: 'x = 2\n', 1: 'print(2 ** 33)\n', 2: 'ddccccc\n', 3: 'ccc\n', 4: 'rr111'}

实际上,集合解析和字典解析都支持前面介绍的扩展列表解析语法,包括if测试:

{line for line in open('script1.py') if line[0] == 'p'}
{'print(2 ** 33)\n'}

{x: line for x,line in enumerate(open('script1.py')) if line[0] == 'p'}
{1: 'print(2 ** 33)\n'}

和列表解析一样,这些都逐行扫描文件并且挑选以字母"p"开始的行。但是通过文件迭代和解析语法使得很多工作自动完成。

还有最后一个值得介绍的迭代环境,在函数调用中用到的一种特殊的*arg形式,它会把一个集合的值解包为单个的参数。它会接受任何可迭代对象,包括文件(参见 第18个学习笔记 了解该调用语法的更多细节)。

def f(a,b,c,d,e): print(a,b,c,d,e,sep = '&')

f(1,2,3,4,5)
1&2&3&4&5

f(*[1,2,3,4,5])
1&2&3&4&5

f(*open('script1.py'))
x = 2
&print(2 ** 33)
&ddccccc
&ccc
&rr111

Python3.0后的新的可迭代对象

这一部分介绍了Python 2.X版本与3.0后版本区别及变化。可以不关注Python 2.X版本的内容,即使本身也没多少。

一个基本的改变是,它比Python 2.X更强调迭代。除了与文件和字典这样的内置类型相关的迭代,字典方法keys、values和items都在Python 3.0后返回可迭代对象,就像内置函数range、map、zip和filter所做的那样。所有这些工具在Python 3.0中都根据请求产生结果。而不是像它们在Python 2.6中那样构建结果列表。

zip('ab','cd')        #可迭代函数,2.6中就是列表
<zip object at 0x00000206C1CC9680>

list(zip('ab','cd'))    #强制显示结果为列表
[('a', 'c'), ('b', 'd')]

在Python 3.0中,它们返回可迭代的对象,根据需要来产生结果。在交互提示模式下(并且可能在某些其他的环境中)显示结果需要额外的录入,这对较大的程序来说很有用,在计算很大的结果列表的时候,像这样的延迟计算会节约内存并避免暂停。

range迭代器

它返回一个迭代器,该迭代器根据需要产生范围中的数字,而不是在内存中构建一个结果列表。

如果需要一个范围列表的话,必须使用list(range(...))来强制一个真正的范围列表(例如,显示结果):

r = range(10)

r
range(0, 10)

i = iter(r)
next(i)
0

list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Python 3.0中的range对象只支持迭代、索引以及len函数。它们不支持任何其他的序列操作(如果需要更多列表工具的话,使用list(...)):

len(r)
10

r[0]
0

next(i)
1

i.__next__()
2

map、zip和filter迭代器

和range类似,map、zip以及filter内置函数在Python 3.0中也转变成迭代器以节约内存空间,而不再在内存中一次性生成一个结果列表。不仅像是在Python 2.X一样处理可迭代对象而且在Python 3.0中返回可迭代结果

和range不同,它们都是自己的迭代器——在遍历其结果一次之后,它们就用尽了。换句话说,不能在它们的结果上拥有在那些结果中保持不同位置的多个迭代器。

和其他迭代器一样,如果确实需要一个列表的话,可以用list(...)来强制一个列表,但是,对于较大的结果集来说,默认的行为可以节省不少内存空间:

m = map(abs,(-5,0,5))    #是迭代器
m
<map object at 0x00000206C1D12590>    

next(m)
5
next(m)
0
next(m)
5
next(m)
Traceback (most recent call last):
  File "<pyshell#215>", line 1, in <module>
    next(m)
StopIteration

for x in m: print(x)    #map迭代器为空 所以不会打印内容


m = map(abs,(-5,0,5))    #再次创建
for x in m: print(x)    #迭代自动调用

5
0
5
list(map(abs,(-5,0,5)))    #强制使用列表
[5, 0, 5]

前面所介绍的zip内置函数,返回以同样方式工作的迭代器:

z = zip((1,2,3),(10,20,30))

z
<zip object at 0x00000206C1D04C80>

list(z)
[(1, 10), (2, 20), (3, 30)]

for y in z: print(y)    #已用尽

z = zip((1,2,3),(10,20,30))    
for y in z: print(y)

(1, 10)
(2, 20)
(3, 30)

z = zip((1,2,3),(10,20,30))

next(z)
(1, 10)

filter内置函数,也是类似的。对于传入的函数返回True的可迭代对象中的每一项,它都会返回该项(Python中的True包括非空的对象):

filter(bool,['life','','hh'])
<filter object at 0x00000206C1CF7370>

list(filter(bool,['life','','hh']))
['life', 'hh']

和上面的大多数工具一样,filter可以接受一个可迭代对象并进行处理,返回一个可迭代对象。

f1 = filter(bool,['life','','hh'])

next(f1)
'life'

多个迭代器VS单个迭代器

range对象与下面小节介绍的内置函数有何不同?

range对象支持len和索引,不是自己的迭代器(手动迭代时使用iter产生一个迭代器),并且,它支持在其结果上的多个迭代器,这些迭代器会记住它们各自的位置:

r = range(3)

next(r)
Traceback (most recent call last):
  File "<pyshell#244>", line 1, in <module>
    next(r)
TypeError: 'range' object is not an iterator

next(i1)
0
next(i1)
1

i2 = iter(r)    #有两个迭代器在同一个range对象上

next(i2)  #i2 迭代与 i1结果不同
0

next(i1)
2

相反,zip、map和filter不支持相同结果上的多个活跃迭代器

z = zip((1,2,3),(10,20,30))
i1 = iter(z)
i2 = iter(z)    #zip有两个迭代器

next(i1)
(1, 10)
next(i1)
(2, 20)
next(i2)    #结果一致
(3, 30)


m = map(abs,(-5,0,5))    #map filter 也是一样结果
i1 = iter(m); i2 = iter(m)
print(next(i1),next(i1),next(i2))
5 0 5

next(i2)    #用尽
Traceback (most recent call last):
  File "<pyshell#261>", line 1, in <module>
    next(i2)
StopIteration

r = range(3)
i1, i2 = iter(r), iter(r)
[next(i1),next(i1),next(i1)]
[0, 1, 2]

next(i2)    #i2没有用尽
0

next(i1)    #可以看出range支持多个迭代器
Traceback (most recent call last):
  File "<pyshell#266>", line 1, in <module>
    next(i1)
StopIteration

在后面章节(第29章)会讲到使用类来编写自己的可迭代对象的时候,将会看到通常通过针对iter调用返回一个新的对象,来支持多个迭代器单个的迭代器一般意味着一个对象返回其自身

字典视图迭代器

之前在字典详解文章中了解了,字典的keys、values和items方法返回可迭代的视图对象,它们一次产生一个结果项,而不是在内存中一次产生全部结果列表。视图项保持和字典中的那些项相同的物理顺序,并且反映对底层的字典做出的修改。

下面介绍其他内容:

d = dict(a=1,b=2,c=3)
d
{'a': 1, 'b': 2, 'c': 3}

k = d.keys()
k
dict_keys(['a', 'b', 'c'])

next(k)    #views本身不是迭代器
TypeError: 'dict_keys' object is not an iterator

i = iter(k)    #手动迭代
next(i)
'a'

for k in d.keys(): print(k,end = ' ')    #所有迭代上下文自动

a b c 

和所有的迭代器一样,可以通过把一个字典视图传递到list内置函数中,从而强制构建一个真正的列表。

k = d.keys()
list(k)    #强制使用列表
['a', 'b', 'c']

v = d.values()
list(v)
[1, 2, 3]

list(d.items())
[('a', 1), ('b', 2), ('c', 3)]

for k,v in d.items(): print(k,v,end = ' ')

a 1 b 2 c 3 

Python 3.0字典仍然有自己的迭代器,它返回连续的键。无需直接在此环境中调用keys:

d
{'a': 1, 'b': 2, 'c': 3}
i = iter(d)
next(i)
'a'

for key in d: print(key,end = ' ')    #不需要调用keys()去迭代,但是keys()本身也可以迭代

a b c 

type(d.keys())
<class 'dict_keys'>

由于keys不再返回一个列表,首先用一个list调用来转换keys视图,或者在一个键视图或字典自身上使用sorted调用,如下所示:

for k in sorted(d.keys()): print(k,d[k],end = ' ')

                
a 1 b 2 c 3 

for k in sorted(d): print(k,d[k],end = ' ')    #最佳做法

                
a 1 b 2 c 3 

其他迭代器主题

后面文章会学习列表解析和迭代器的更多内容。以及学习类的时候会再遇到。

在后面,将会看到:

·使用yield语句,用户定义的函数可以转换为可迭代的生成器函数。

·当编写在圆括号中的时候,列表解析转变为可迭代的生成器表达式。

·用户定义的类通过__iter__或__getitem__运算符重载变得可迭代。

特别地,使用类定义的用户定义的迭代器,允许在这里所遇到的任何迭代环境中使用任意对象和操作。

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兴焉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值