Learning Python Part II 之 迭代和推導式

   for循環能夠遍歷列表、元組、字符串等,然而不僅僅有這些,更廣泛的講,for循環能夠遍歷所有可迭代對象( iterable objects )。可迭代對象包括物理序列( physical sequence )和 虛擬序列 (virtual sequence )
   物理序列:指物理意義上按順序存儲的數列,例如列表、元組等
   虛擬序列:指在for循環之類的迭代工具中每次產生一個結果
   迭代工具(iteration tool):例如for循環、列表推導式(list comprehension)、 in 、map等。

文件迭代器(File iterator):

    任何對象有 __next__ 方法並且在結束時能夠拋出 StopIteration 異常的都叫做迭代器(iterator), 這類對象也能夠通過for循環或者其他迭代工具一步一步的獲取結果。因爲所有的迭代工具在內部的工作原理都是在每次迭代中調用 __next__ 方法並且通過抓取 StopIteration 異常決定什麼時間結束。
>>> f = open('/home/coder/Documents/script.py')
>>> f.__next__() 
'import sys\n'
>>> f.__next__()
'print(sys.path)\n'
>>> f.__next__()
'x = 2\n'
>>> f.__next__()
'print(x ** 32)\n'
>>> f.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> for line in open('/home/coder/Documents/script.py'):
...     print(line.upper(), end='') 
... 
IMPORT SYS
PRINT(SYS.PATH)
X = 2
PRINT(X ** 32)
第一個例子是文件對象調用 __next__ 方法輸出下一行,並在結束時拋出 StopIteration 異常,這樣的迭代器也能夠通過迭代工具實現同樣的效果,即第二個例子。這種方法是一行一行讀取文件的最好方法。文件對象的readline()方法和readlines()方法,值得注意的是readline()在結束時讀取空字符串,而readlines()方法一次加載完整個文件,當讀取大文件時會遇到麻煩。當然,我們也可以用while實現同樣的效果:
>>> while True:
...     line = f.readline()
...     if not line: break
...     print(line.upper(), end='')
... 
IMPORT SYS
PRINT(SYS.PATH)
X = 2
PRINT(X ** 32)
但是,while循環的版本是在Python虛擬機中運行的python字節代碼,迭代器的版本在python內部是以C語言的速度運行的,,運行起來更快。

迭代協議(iteration protocol):

當for循環開始後,它先把可迭代對象(iterable object)傳遞給內建函數iter,然後獲得一個迭代器(iterator)。iter 返回的對象有 next()方法。iter函數在內部運行 __iter__ 方法,就像next() 在內部運行__next__方法一樣。下圖是一個完整的迭代協議:

這裏寫圖片描述

一些對象既是迭代工具(iteration context tool)也是可迭代對象,包括生成器(generator expression)、Python3.x中的map ,zip,range和一些字典方法-----爲了避免一次把所有結果加載到內存中。

通過迭代協議可知,如果用for循環迭代一個列表,第一步如何做很明顯:
>>> L =[1, 2, 3]
>>> I = iter(L)
>>> I.__next__()
1
>>> I.__next__()
2
>>> I.__next__()
3
>>> I.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

初始化的第一步對與文件對象來說並不需要,因爲文件對象就是他們自己的迭代器,因爲他們僅僅支持一次迭代,他們不能夠指針倒退去多次掃描,文件有自己的next方法。

>>> f = open('/home/coder/Documents/script.py')
>>> iter(f) is f
True
>>> iter(f) is f.__iter__()
True
>>> f.__next__()
'import sys\n'

列表和其他一些內置對象並沒有自己的迭代器,他們支持多重迭代,例如他們可以在嵌套循環中多重迭代並且從不同的位置開始,對於這樣的對象,我們開始迭代之前必須要調用 iter

>>> L = [1, 2, 3]
>>> iter(L) is L
False
>>> L.__next__()
AttributeError: 'list' object has no attribute '__next__'
>>> I = iter(L)
>>> I.__next__()
1
>>> next(I)
2

手動迭代(Manual iteration):

>>> L = [1, 2, 3]
>>> for x in L:    #自動迭代
...     print(x**2,end='')    #獲得iter,調用__next__,捕獲異常
... 
149
>>> I = iter(L)   #手動迭代,這一步在for內自動完成
>>> while True:
...     try:      #捕獲異常
...         x = next(I) #調用next
...     except StopIteration:
...         break
...     print(x**2,end='')
... 
149

其他可迭代對象:

在新版本的Python中,字典時可迭代的,每次自動返回一個鍵,這意味着

>>> D = {'a':1, 'b':2, 'c':3}
>>> for key in D:
...     print(key, D[key])
... 
a 1
b 2
c 3
>>> R = range(5)
>>> R              # Ranges 在Python3.x中是可迭代的
range(0, 5)
>>> I = iter(R)    # 通過迭代協議獲取值
>>> next(I)
0
>>> next(I)
1
>>> list(range(5)) # 或者通過list()方法一次獲取所有值
[0, 1, 2, 3, 4]
>>> E = enumerate('spam')    # enumerate也是可迭代的
>>>> E
<enumerate object at 0x00000000029B7678>
>>> I = iter(E)
>>> next(I)        # 通過迭代獲取值protocol
(0, 's')
>>> next(I)
(1, 'p')
>>> list(enumerate('spam'))      # 一次獲得所有值
[(0, 's'), (1, 'p'), (2, 'a'), (3, 'm')]

列表推導式(list comprehension):

事實上,列表推導式並不是不可或缺的,我們可以用for邏輯塊實現同樣的功能,但是列表推導式看起來更簡單明瞭,運行起來更快。當我們思考何如對一個序列中的每個元素執行相同的操作時,列表推導式就是一個很好的方法。

>>>L = [x**2 for x in range(10)]
>>> [x + y for x in 'abd' for y in 'imn']
['ai', 'am', 'an', 'bi', 'bm', 'bn', 'di', 'dm', 'dn']

列表推導式應用於文件:

>>> lines = [line.rstrip() for line in open('/home/coder/Documents/script.py')]
>>> lines
['import sys', 'print(sys.path)', 'x = 2', 'print(x ** 32)']
>>> lines = [line.rstrip() for line in open('/home/coder/Documents/script.py') if line[0] == 'p']
>>> lines
['print(sys.path)', 'print(x ** 32)']
>>> [line.rstrip() for line in open('/home/coder/Documents/script.py') if line.rstrip()[-1].isdigit()]
['x = 2']
>>> len([line for line in open('/home/coder/Documents/script.py') if line.strip != '' ])
4

Python3.X中新的可迭代對象:

Range:
在3.x中,range() 函數返回的是一個可迭代對象,它會根據需要產生結果,而不是生成一個儲存在內存中列表。

>>> R = range(10)
>>> R
range(0, 10)
>>> I = iter(R)
>>> next(I)
0
>>> list(R)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

在3.x中range()僅支持 迭代、索引和len(),不支持其他序列操作

map、zip、filter:
map,zip,filter不僅能夠處理迭代,而且他們返回的結果也是可迭代的,和reange不同,他們是自己的迭代器(還記得文件對象麼?他也是自己的迭代器)

>>> M = map(abs,(-1, 0, 1))
>>> M
<map object at 0x7fe05c83cac8>
>>> next(M)
1
>>> next(M)
0
>>> next(M)
1
>>> next(M)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> M = map(abs,(-1, 0, 1))
>>> for x in M:
...     print(x)
... 
1
0
1
>>> Z = zip((1, 2, 3),(10, 20, 30))
>>> Z
<zip object at 0x7fe05c847188>
>>> list(Z)
[(1, 10), (2, 20), (3, 30)]
>>> Z = zip((1, 2, 3),(10, 20, 30))
>>> for pair in Z: print(pair)
... 
(1, 10)
(2, 20)
(3, 30)
>>> Z = zip((1, 2, 3),(10, 20, 30))
>>> next(Z)
(1, 10)

以上內容來源於Learning Python 5th–Partiii— Chapter14

28092017總結:

  1. 每個內置工具都是從左到右使用迭代協議掃描對象
  2. 手動迭代分兩步,第一步調用iter返回迭代器,第二部調用__next__並捕獲StopIteration,有自己的迭代器的可迭代對象不需要第一步,如文件、map、zip等
  3. for循環之類的迭代工具能夠自動完成2的第一步,所以迭代工具可以處理可迭代對象和迭代器,並且看不出來差別
  4. 在很多情況下當迭代時僅有一次掃描的時候,可迭代對象和迭代器沒有區別,迭代器只是臨時產生並被迭代工具使用
  5. 可迭代對象的作用是在需要的時候產生結果而不用一次吧所有的結果都加載進內存,節省空間,提高速度(此條存疑)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章