关闭

Python——迭代器和解析(1)

标签: Python迭代器解析
2224人阅读 评论(0) 收藏 举报
分类:

迭代器:初探

在之前介绍过,for循环可以用于Python中任何序列类型,包括列表、元组以及字符串,如下所示:

>>> for x in [1,2,3,4]:print(x**2,end=" ")

1 4 9 16 
>>> for x in (1,2,3,4):print(x**3,end = ' ')

1 8 27 64 
>>> for x in 'spam' :print(x*2,end=' ')

ss pp aa mm 
实际上,for循环甚至比这更为通用:可用于任何可迭代的对象。
实际上,对Python中所有会从左至右扫描对象的迭代工具而言都是如此,这些迭代工具包括了for循环、列表解析、in成员关系测试以及map内置函数等

'可迭代对象'在语言设计中很普遍,基本上,这就是序列观念的通用化:如果对象是实际保存的序列,或者可以在迭代工具环境中一次产生一个结果的对象,就看做时可迭代的。
===============================================================================
文件迭代器

已打开的文件对象中有个方法名为readline,可以一次从一个文件中读取一行文本,每次调用readline方法时,就会前进到下一行,到达文件末尾时,就会返回空字符串:

>>> f = open(r'F:\002calc.py')
>>> f.readline()
'temp = input("Please input a number:\\n")\n'
>>> f.readline()
'num = int(temp)\n'
>>> f.readline()
'if 1<=num<=100:\n'
>>> f.readline()
'    print("your sister is beautiful!")\n'
>>> f.readline()
'else:\n'
>>> f.readline()
'    print("your oldfather is ugly!")\n'
>>> f.readline()
''
>>> f.readline()
''
现在,文件对象中也有一个方法名,为__next__,差不多有同样的效果,每次调用时,就会返回文件中的下一行。
值得注意的区别是,到达文件末尾时,__next__方法会引发内置的StopIteration异常,而不是返回空字符串。

>>> f = open(r'F:\002calc.py')
>>> f.__next__()
'temp = input("Please input a number:\\n")\n'
>>> f.__next__()
'num = int(temp)\n'
>>> f.__next__()
'if 1<=num<=100:\n'
>>> f.__next__()
'    print("your sister is beautiful!")\n'
>>> f.__next__()
'else:\n'
>>> f.__next__()
'    print("your oldfather is ugly!")\n'
>>> f.__next__()
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    f.__next__()
StopIteration
这个接口就是Python中所谓的迭代协议有__next__方法的对象会前进到下一个结果,而在一系列结果的末尾时,则会引发StopIteration。

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

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

>>> for line in open(r'F:\002calc.py'):
	print(line.upper(),end='')

	
TEMP = INPUT("PLEASE INPUT A NUMBER:\N")
NUM = INT(TEMP)
IF 1<=NUM<=100:
    PRINT("YOUR SISTER IS BEAUTIFUL!")
ELSE:
    PRINT("YOUR OLDFATHER IS UGLY!")
注意:这里的print使用end=' '来抑制添加一个'\n',因为行字符串已经有了一个。
上例是读取文本文件的最佳方式,原因有三点:这是最简单的写法,运行最快,并且从内存使用情况来看也是最好的。
相同效果的原始方式,是以for循环调用文件的readlines方法,将文件内容加载到内存,做成行字符串列表。
>>> open(r'F:\002calc.py').readlines()
['temp = input("Please input a number:\\n")\n', 'num = int(temp)\n', 'if 

1<=num<=100:\n', '    print("your sister is beautiful!")\n', 'else:\n', '    

print("your oldfather is ugly!")\n'] 
>>> for line in open(r'F:\002calc.py').readlines():
	print(line.upper(),end='')

	
TEMP = INPUT("PLEASE INPUT A NUMBER:\N")
NUM = INT(TEMP)
IF 1<=NUM<=100:
    PRINT("YOUR SISTER IS BEAUTIFUL!")
ELSE:
    PRINT("YOUR OLDFATHER IS UGLY!")
这种方法比较耗内存。
===============================================================================
手动迭代:iter和next

为了支持手动迭代代码(用于较少的录入),Python3.0还提供了一个内置函数next,它会自动调用一个对象的__next__方法。
给定一个可迭代对象X,自身有__next__方法,调用next(X)等同于X.__next__(),但前者简单很多。例如:

>>> f = open(r'F:\002calc.py')
>>> next(f)
'temp = input("Please input a number:\\n")\n'
>>> next(f)
'num = int(temp)\n'
从技术角度来讲,迭代协议还有一点值得注意。当for循环开始时,会通过它传给iter内置函数,以便从可迭代对象中获得一个迭代器,返回对象中有需要的next方法。
我们看看for循环内部如何处理列表这类内置序列类型:
>>> L = [1,2,3]
>>> I = iter(L)
>>> I.next()
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    I.next()
AttributeError: 'list_iterator' object has no attribute 'next'
>>> next(I)
1
>>> next(I)
2
>>> next(I)
3
>>> next(I)
Traceback (most recent call last):
  File "<pyshell#39>", line 1, in <module>
    next(I)
StopIteration
最初的一步对于文件来说不是必需的,因为文件对象就是自己的迭代器。也就是说,文件有自己的__next__方法,因此不需要像这样返回一个不同的对象
>>> f = open(r'F:\002calc.py')
>>> iter(f) is f
True
>>> f.__next__()
'temp = input("Please input a number:\\n")\n'
>>> next(f)
'num = int(temp)\n'
列表以及很多其他的内置对象,不是自身的迭代器,因为它们支持多次打开迭代器。对于这样的对象,我们必须调用iter来启动迭代:
>>> L = [1,2,3]
>>> iter(L) is L
False
>>> L.__next__()
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    L.__next__()
AttributeError: 'list' object has no attribute '__next__'

>>> I = iter(L)
>>> I.__next__()
1
>>> next(I)
2
尽管Python迭代工具自动调用这些函数,我们也可以使用它们来手动地应用迭代协议。
如下的交互展示了自动和手动迭代之间的对等性:

>>> L = [1,2,3]
>>> for x in L:
	print(x**2,end=' ')

	
1 4 9 
>>> L
[1, 2, 3]
>>> I = iter(L)
>>> while True:
	try:
		x = next(I)
	except StopIteration:
		break
	print(x**2,end=' ')

	
1 4 9 
===============================================================================

其他内置类型迭代器

除了文件以及列表这样的实际的序列外,其他类型也有其使用的迭代器。例如,遍历字典键的经典方法时明确地获取其键的列表。

>>> D = {'a':1,'b':2,'c':3}
>>> for key in D.keys():
	print(key,D[key])

	
b 2
a 1
c 3
>>> I = iter(D)
>>> next(I)
'b'
>>> next(I)
'a'
>>> next(I)
'c'
>>> next(I)
Traceback (most recent call last):
  File "<pyshell#73>", line 1, in <module>
    next(I)
StopIteration

不过,在最近的Python版本中,字典有一个迭代器,在迭代环境中,会自动一次返回一个键,如上例所示↑

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

>>> for key in D:
	print(key,D[key])

	
b 2
a 1
c 3
迭代协议也是我们必须把某些结果包装到一个list调用中以一次性看到它们的值的原因。可迭代的对象一次返回一个结果,而不是一个实际的列表:
>>> R = range(5)
>>> R
range(0, 5)
>>> I = iter(R)
>>> next(I)
0
>>> next(I)
1
>>> next(I)
2

>>> list(range(5))
[0, 1, 2, 3, 4]
既然对这一协议已经有了比较深入的理解,我们应该看到它是如何说明前面所说的enumerate工具能够以其方式工作的原因:

>>> E = enumerate('spam')
>>> E
<enumerate object at 0x007C1AF8>
>>> I = iter(E)
>>> next(I)
(0, 's')
>>> next(I)
(1, 'p')
>>> next(I)
(2, 'a')
>>> next(I)
(3, 'm')
>>> list(enumerate('spam'))
[(0, 's'), (1, 'p'), (2, 'a'), (3, 'm')]
我们通常不会看到这种机制,因为for循环为我们自动遍历结果。实际上,Python中可以从左向右扫描的所有对象都以同样的方式实现了迭代协议。

===============================================================================

列表解析:初探

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

之前我们知道了在遍历一个列表的时候,如何使用range来修改它:

>>> L = [1,2,3,4,5]
>>> for i in list(range(len(L))):
	L[i] += 10

	
>>> L
[11, 12, 13, 14, 15]
这是有效的,但是正如我们所提到的,它可能不是Python中的优化的最佳实践。如今,列表解析表达式使得早先许多例子变得过时了。
例如,我们可以用产生所需的结果列表的一个单个表达式来替代该循环:
>>> L = [1,2,3,4,5]
>>> L = [x+10 for x in L]
>>> L
[11, 12, 13, 14, 15]
结果相同,但这个需要更少的代码,并且运行得更快
---------------------------------------------------------------------------------------------------------------------------------------------

列表解析基础知识

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

===============================================================================

在文件上使用列表解析

让我们来看一下列表解析的另一个常见实例,从而更详细地了解它。
文件对象有一个readlines方法,它能一次性地把文件载入到行字符串的一个列表中:

>>> f = open(r'F:\002practice.py')
>>> lines = f.readlines()
>>> lines
['#print("Please input your name:")\n', 'name = input("Please input your name:\\n");\n', 'print("Hello "+name)\n']
这是有效的,因为结果中的行在末尾都包含了一个换行符合(\n),对于很多程序员来说,换行符号很讨厌,我们必须小心避免打印的时候留下双倍的空白,等等
如果我们可以一次性去除这些换行符号,岂不是好事吗?
当我们开始考虑在一个序列中的每项执行一个操作时,都可以考虑使用列表解析。

>>> lines = [line.rstrip() for line in lines]
>>> lines
['#print("Please input your name:")', 'name = input("Please input your name:\\n");', 'print("Hello "+name)']
由于列表解析像for循环语句一样是一个迭代环境,我们甚至不必提前打开文件:
>>> lines = [line.rstrip() for line in open(r'F:\002practice.py')]
>>> lines
['#print("Please input your name:")', 'name = input("Please input your name:\
\n");', 'print("Hello "+name)']
运用列表解析可以做很多事情
---------------------------------------------------------------------------------------------------------------------------------------------
扩展的列表解析语法

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

例如:假设我们想要重复上述的文件扫描示例,但是,我们只需要收集以字母p开头的那些行(可能每一行的第一个字母是某种类型的动作代码)。

向表达式添加一条if过滤字句来实现:

>>> lines = [line.rstrip() for line in open(r'F:\002practice.py') if line[0] == 'p']
>>> lines
['print("Hello "+name)']
这条if子句检查从文件中读取的每一行,看它的第一个字符是否是p,如果不是,从结果列表中省略该行。
这是一个相当大的表达式,但是可以把它转换成for循环语句,但for循环语句代码量更多而且运行更慢。


如果我们需要的话,列表解析可以变得更复杂————例如,它们可能包含嵌套的循环,也可能被编写为一系列的for字句。

实际上,它们的完整语法允许任意数目的for子句,每个子句有一个可选的if子句。例如下面例子构建了一个x+y连接的列表,把一个字符串的每个x和另一个字符串的每个y连接起来。它有效地收集了两个字符串的字符的排列(类似于笛卡尔乘积)

>>> [x+y for x in 'abc' for y in '123']
['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']
>>> [x+y for (x,y) in zip('abc','123')]
['a1', 'b2', 'c3']

【注意:应用于列表解析的嵌套循环,它和zip是不一样的,看上例↑】

===============================================================================

其他迭代环境

后面我们将会知道,用户定义的类也可以实现迭代协议

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

>>> uppers = [line.upper() for line in open(r'F:\002practice.py')]
>>> uppers
['#PRINT("PLEASE INPUT YOUR NAME:")\n', 'NAME = INPUT("PLEASE INPUT YOUR NAME:\\N");\n', 'PRINT("HELLO "+NAME)\n']
>>> map(str.upper,open(r'F:\002practice.py'))
<map object at 0x007C65B0>
>>> list(map(str.upper,open(r'F:\002practice.py')))
['#PRINT("PLEASE INPUT YOUR NAME:")\n', 'NAME = INPUT("PLEASE INPUT YOUR NAME:\\N");\n', 'PRINT("HELLO "+NAME)\n']
>>> 'y = 2 \n' in open(r'F:\002practice.py')
False
之前学过map调用,它是一个内置函数,它把一个函数调用应用于传入的可迭代对象的每一项。map类似于列表解析,但是它更有局限性,因为它需要一个函数而不是一个任意的表达式。
Python中还包含了各种处理迭代的其他内置函数:sorted排序可迭代对象中的各项,zip组合可迭代对象中的各项,enumerate根据相对位置来配对可迭代对象中的项filter选择一个函数为真的项,reduce针对可迭代对象中成对的项运行一个函数。所有的这些都接受一个可迭代的对象,并且在Python3.0中,zip、enumerate和filter也像map一样返回一个可迭代对象(全部显示需要用list):

>>> sorted(open(r'F:\002practice.py'))
['#print("Please input your name:")\n', 'name = input("Please input your name:\\n");\n', 'print("Hello "+name)\n']
>>> list(zip(open(r'F:\002practice.py'),open(r'F:\002practice.py')))
[('#print("Please input your name:")\n', '#print("Please input your name:")\n'), ('name = input("Please input your name:\\n");\n', 'name = input("Please input your name:\\n");\n'), ('print("Hello "+name)\n', 'print("Hello "+name)\n')]
>>> list(enumerate(open(r'F:\002practice.py')))
[(0, '#print("Please input your name:")\n'), (1, 'name = input("Please input your name:\\n");\n'), (2, 'print("Hello "+name)\n')]
sorted是应用了迭代协议的一个内置函数,它就像是最初的列表的sort方法,但是它返回一个新的排序的列表作为结果并且可以在任何可迭代对象上运行
【注意:和map以及其他函数不同,sorted在Python3.0中返回一个真正的列表而不是一个可迭代对象】

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

>>> sum([1,2,3,4])
10
>>> any(['spam','','ni'])
True
>>> all(['spam','','ni'])
False
>>> max([1,2,3,4])
4
>>> min({1,2,3,4})
1
【注意:all要求所有为真才返回真,any是如果有一项为真则返回真】

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

>>> list(open(r'F:\002practice.py'))
['#print("Please input your name:")\n', 'name = input("Please input your name:\\n");\n', 'print("Hello "+name)\n']
>>> tuple(open(r'F:\002practice.py'))
('#print("Please input your name:")\n', 'name = input("Please input your name:\\n");\n', 'print("Hello "+name)\n')
>>> '&&'.join(open(r'F:\002practice.py'))
'#print("Please input your name:")\n&&name = input("Please input your name:\\n");\n&&print("Hello "+name)\n'
>>> a,b,c = open(r'F:\002practice.py')
>>> a,c
('#print("Please input your name:")\n', 'print("Hello "+name)\n')
>>> a,*b = open(r'F:\002practice.py')
>>> a,b
('#print("Please input your name:")\n', ['name = input("Please input your name:\\n");\n', 'print("Hello "+name)\n'])
早先我们见到过内置的dict调用接受一个可迭代的zip结果。为此,我们来看看set调用,以及Python3.0新增的集合解析和字典解析表达式:
>>> set(open(r'F:\002practice.py'))
{'name = input("Please input your name:\\n");\n', '#print("Please input your name:")\n', 'print("Hello "+name)\n'}
>>> {line for line in open(r'F:\002practice.py')}
{'name = input("Please input your name:\\n");\n', '#print("Please input your name:")\n', 'print("Hello "+name)\n'}
>>> {i:line for i,line in enumerate(open(r'F:\002practice.py'))}
{0: '#print("Please input your name:")\n', 1: 'name = input("Please input your name:\\n");\n', 2: 'print("Hello "+name)\n'}
可以看到集合解析和字典解析与列表解析类似;
>>> {key:value for (key,value) in zip([1,2,3],['a','b','c'])}
{1: 'a', 2: 'b', 3: 'c'}
集合解析和字典解析都支持前面我们所说的列表解析的扩展语法:
>>> {line for line in open(r'F:\002practice.py') if line[0]=='p'}
{'print("Hello "+name)\n'}
>>> {i:line for i,line in enumerate(open(r'F:\002practice.py'))if line[0]=='p'}
{2: 'print("Hello "+name)\n'}
还有一个值得介绍的迭代工具,尽管现在介绍有点早。
在后面,将学习在函数调用中用到的一种特殊的*arg形式,它会把一个集合的值解包为单个的参数。
现在我们可以预计,它也会接受任何可迭代对象,包括文件:

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

>>> f(1,2,3)
1&2&3
>>> f(*[1,2,3])
1&2&3
>>> f(*open(r'F:\002practice.py'))
#print("Please input your name:")
&name = input("Please input your name:\n");
&print("Hello "+name)
将在下一部分学习filter内置函数,也是类似的,对于传入的函数返回True的可迭代对象中的每一项,他都会返回该项。
>>> filter(bool,['spam','','ni'])
<filter object at 0x037AE2B0>
>>> list(filter(bool,['spam','','ni']))
['spam', 'ni']
===============================================================================
多个迭代器VS单个迭代器

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

>>> R = range(3)
>>> next(R)

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


>>> I1 = iter(R)
>>> next(I1)
0
>>> next(I1)
1
>>> I2 = iter(R)
>>> next(I2)
0
>>> next(I2)
1
相反的是,zip,map和filter不支持相同结果上的多个活跃迭代器:
>> Z = zip((1,2,3),(10,11,12))
>>> Z1 = iter(Z)
>>> Z2 = iter(Z)
>>> next(Z1)
(1, 10)
>>> next(Z1)
(2, 11)
>>> next(Z2)
(3, 12)

>>> M = map(abs,(-2,0,1))
>>> M1 = iter(M)
>>> M2 = iter(M)
>>> next(M1)
2
>>> next(M1)
0
>>> next(M2)
1
当以后使用类来编写自己的可迭代对象的时候,将会看到通常针对iter调用返回一个新的对象,来支持多个迭代器,单个迭代器一般意味着一个对象返回其自身!
===============================================================================

字典视图迭代器

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

>>> D = dict(a=1,b=2,c=3)
>>> D
{'b': 2, 'c': 3, 'a': 1}
>>> K = D.keys()
>>> K
dict_keys(['b', 'c', 'a'])
>>> next(K)
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    next(K)
TypeError: 'dict_keys' object is not an iterator
>>> I = iter(K)
>>> next(I)
'b'
>>> next(I)
'c'
>>> for k in D.keys():
	print(k,end=' ')

	
b c a 
和所有的迭代器一样,我们总可以通过把一个Python3的字典视图传递到list内置函数中,从而强制构建一个真正的列表。然而,这通常不是必须的
>>> D
{'b': 2, 'c': 3, 'a': 1}
>>> K = D.keys()
>>> list(K)
['b', 'c', 'a']
>>> V = D.values()
>>> list(V)
[2, 3, 1]
>>> V
dict_values([2, 3, 1])
>>> list(D.items())
[('b', 2), ('c', 3), ('a', 1)]
>>> for (k,v) in D.items():
	print(k,v,end=' ')

	
b 2 c 3 a 1
当然,字典也有其自身的迭代器,它返回连续的键,因此,无需直接在循环中调用keys方法:
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> for key in D:
	print(key)

	
a
c
b
最后,再次提醒,由于keys不再返回一个列表,按照排序的键来扫描一个字典的传统编码模式在Python3中不再有效。相反,首先用一个list调用来转换keys视图,或者在一个键视图或字典自身上使用sorted调用,如下:
>>> D
{'b': 2, 'c': 3, 'a': 1}
>>> for k in sorted(D.keys()):
	print(k,D[k],end=' ')

	
a 1 b 2 c 3
>>> D
{'b': 2, 'c': 3, 'a': 1}
>>> for k in sorted(D):
	print(k,D[k],end=' ')

	
a 1 b 2 c 3 
===============================================================================
其他迭代主题

之后将会学习列表解析和迭代器的更多内容,后面将会看到:
1.使用yield语句,用户定义的函数可以转换为可迭代的生成器函数
2.当编写在圆括号的时候,列表解析转变为可迭代的生成器表达式
3.用户定义的类通过__iter__或__getitem__运算符重载变得可迭代。
特别的,使用类定义的用户定义的迭代器,允许我们这里所遇到的任何迭代环境中使用任意对象和操作。

===============================================================================

问题

1:for循环和迭代器之间有什么关系?

for循环会使用迭代协议来遍历迭代的对象中的每一个项。for循环在开始时会通过它传给iter内置函数,以便从可迭代对象中获得一个迭代器,返回对象中有需要的next方法。for循环会在每次迭代中调用该对象的__next__方法(由next内置函数运行),而且会捕捉StopIteration异常,从而决定何时停止循环。支持这种模式的任何对象,都可以用于for循环以及其他迭代环境中。

2.for循环和列表解析直接有什么关系?

两者都是迭代工具。列表解析是执行常见for循环任务的简明并且高效的方法:对可迭代对象内所有元素应用一个表达式,并收集其结果。你可以把列表解析转换成for循环,而列表解析表达式的一部分的语法看起来就像是for循环的首行。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:340812次
    • 积分:5336
    • 等级:
    • 排名:第5085名
    • 原创:152篇
    • 转载:39篇
    • 译文:5篇
    • 评论:46条
    GitHub
    博客专栏
    JavaWeb框架

    文章:6篇

    阅读:13869
    XML

    文章:7篇

    阅读:26354
    Servlet

    文章:24篇

    阅读:61595
    Python3

    文章:44篇

    阅读:77030
    最新评论