python中的迭代器和生成器

在咱們學習迭代器和生成器以前的時候,咱們要先搞清楚幾個概念:html

  • 迭代協議:__next__方法會前進道下一個結果,並且在一系列結果的末尾時,會引起 StopIteration異常的對象.
  • 可迭代對象: 實現了 __iter__方法的對象
  • 迭代器: 實現了 __iter____next__方法的對象
  • 生成器: 經過生成器表達式或者yeild關鍵字實現的函數.

這裏不太好理解,咱們借用一個圖 python

可迭代對象

須要注意的是可迭代對象不必定是迭代器.好比列表類型和字符串類型都是可迭代對象,可是他們都不是迭代器.web

In [1]: L1 = [1,2,3,4]

In [2]: type(L1)
Out[2]: list

In [3]: L1_iter=L1.__iter__()

In [4]: type(L1_iter)
Out[4]: list_iterator

可是對於容器以及文件這樣的可迭代對象來講的話,他們都實現了一個__iter__方法. 這個方法能夠返回一個迭代器. app

迭代器中的__next__方法,next()方法和for語句

首先迭代器中都實現了__next__()方法. 咱們能夠直接調用迭代器的__next__方法來獲得下一個值. 好比:編輯器

In [10]: L1_iter.__next__()
Out[10]: 1

In [11]: next(L1_iter)
Out[11]: 2

注意這裏,next()方法也是去調用迭代器內置的__next__方法. 因此這兩種操做是同樣的. 可是在平常使用的時候,咱們不會直接去調用next()方法來使用生成器.函數

更多的操做是經過for語句來使用一個生成器.工具

就下面這兩段代碼來看,其做用上是等效的.post

L1 = [1, 2, 3, 4]

for x in L1:
print(x, end=" ")

print("\nthe same result of those two statements!")

L1_iter = L1.__iter__()
while True:
try:
x = L1_iter.__next__()
print(x, end=" ")
except StopIteration:
break

可是實際上,使用for語句在運行速度可能會更快一點. 由於迭代器在Python中是經過C語言實現的. 而while的方式則是以Python虛擬機運行Python字節碼的方式來執行的.性能

畢竟...你大爺永遠是你大爺. C語言永遠是你大爺...學習

列表解析式

列表解析式或者又叫列表生成式,這個東西就比較簡單了. 舉個簡單的例子,好比咱們要定義一個1-9的列表. 咱們能夠寫L=[1,2,3,4,5,6,78,9] 一樣咱們也能夠寫L=[x for x in range(10)]

再舉一個簡單的例子,咱們如今已經有一個列表L2=[1,2,3,4,5] 咱們要獲得每一個數的平方的列表. 那麼咱們有兩種作法:

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

# statement1
for i in range(len(L2)):
L2[i] = L2[i]*L2[i]
#statement2
L3 = [x*x for x in L2]

顯然從代碼簡潔渡上來講 第二種寫法更勝一籌. 並且它的運算速度相對來講會更快一點(每每速度會快一倍.P.S.書上的原話,我沒有驗證...). 由於列表解析式式經過生成器來構造的,他們的迭代是python解釋器內部以C語言的速度來運行的. 特別是對於一些較大的數據集合,列表解析式的性能優勢更加突出.

列表解析式還有一些高端的玩法. 好比能夠與if語句配合使用:

L4 = [1, 2, 3, 4, 5, 6, 7, 8, 9]

L5 = [x for x in L4 if x % 2 == 0]

還可使用for語句嵌套;

L6=[1,2,3,4,5]
L7=['a','b','c','d','e']

L8=[str(x)+y for x in L6 for y in L7]

或者能夠寫的更長

L9=[(x,y) for x in range(5) if x % 2 ==0 for y in range(5) if y %2 ==1]

一個更復雜的例子

M = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(M[1][1])
print(M[1])
print([row[1] for row in M])
print([M[row][1] for row in (0,1,2)])
print([M[i][i] for i in range(len(M))])

一樣的,咱們能夠經過for語句來實現上述的功能. 可是列表解析式想對而言會更加簡潔.

另外map函數比等效的for循環要更快,而列表解析式每每會比map調用還要快一點.

python3中新的可迭代對象

python3相好比python2.x來講,它更強調迭代. 除了文件,字典這樣的內置類型相關的迭代外. 字典方法keys,values都在python3中返回可迭代對象. 就像map,range,zip方法同樣. 它返回的並非一個列表. 雖然從速度和內存佔用上更有優點,可是有時候咱們不得不使用list()方法使其一次性計算全部的結果.

range迭代器

In [12]: R=range(10)

In [13]: R
Out[13]: range(0, 10)

In [14]: I = iter(R)

In [15]: next(I)
Out[15]: 0

In [16]: R[5]
Out[16]: 5

In [17]: len(R)
Out[17]: 10

In [18]: next(I)
Out[18]: 1

range的話,僅支持迭代,len()和索引. 不支持其餘的序列操做. 因此若是須要更多的列表工具的話,使用list()...

map,zip和filter迭代器

和range相似, map,zip和filter在python3.0中也轉變成了迭代器以節約內存空間. 可是它們和range又不同.(確切來講是range和它們不同) 它們不能在它們的結果上擁有在那些結果中保持不一樣位置的多個迭代器.(第四版書上原話,看看這叫人話嗎...)

翻譯一下就是,map,zip和filter返回的都是正經迭代器,不支持len()和索引. 以map爲例作個對比.

In [20]: map_abs = map(abs,[1,-3,4])

In [21]: M1 = iter(map_abs)

In [22]: M2=iter(map_abs)

In [23]: next(M1)
Out[23]: 1

In [24]: next(M2)
Out[24]: 3

而range不是正經的迭代器. 它支持在其結果上建立多個活躍的迭代器.

In [25]: R=range(10)

In [26]: r1 = iter(R)

In [27]: r2=iter(R)

In [28]: next(r1)
Out[28]: 0

In [29]: next(r2)
Out[29]: 0

字典中的迭代器

一樣的,python3中字典的keys,values和items方法返回的都是可迭代對象.而非列表.

In [30]: D = dict(a=1,b=2,c=3)

In [31]: D
Out[31]: {'a': 1, 'b': 2, 'c': 3}

In [32]: K = D.keys()

In [33]: K
Out[33]: dict_keys(['a', 'b', 'c'])

In [34]: next(K)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-34-02c2ef8731e9> in <module>
----> 1 next(K)

TypeError: 'dict_keys' object is not an iterator

In [35]: i = iter(K)

In [36]: next(i)
Out[36]: 'a'

In [37]: for k in D.keys(): print(k,end=' ')

一樣的,咱們能夠利用list()函數來顯式的把他們變成列表. 另外,python3中的字典仍然有本身的迭代器. 它返回連續的見. 所以在遍歷的時候,無需顯式的調用keys().

In [38]: for key in D: print(key,end=' ')
a b c

生成器

生成器能夠說是迭代器的一個子集. 有兩種建立方法:

  • 生成器函數: 編寫常規的以def爲關鍵字的函數,使用yield關鍵字返回結果. 在每一個結果之間掛起和繼續他們的狀態.
  • 生成器表達式: 相似前面所說的列表解析式.

生成器函數關鍵字 yeild

狀態掛起

和返回一個值而且退出的常規函數不一樣,生成器函數自動在生成值得時刻掛起並繼續函數的執行. 它在掛起時會保存包括整個本地做用域在內的全部狀態. 在恢復執行時,本地變量信息依舊可用.

生成器函數使用yeild語句來掛起函數並想調用者發送回一個值.以後掛起本身. 在恢復執行的時候,生成器函數會從它離開的地方繼續執行.

生成器函數的應用

def gensquares(num):
for i in range(num):
yield i**2

for i in gensquares(5):
print(i)

一樣的,生成器其實也是實現了迭代器協議. 提供了內置的__next__方法.

上面這個例子若是咱們要改寫爲普通函數的話,能夠寫成以下的樣子.

def buildsquares(num):
res = []
for i in range(num):
res.append(i**2)
return res

for i in buildsquares(5):
print(i)

看上去實現的功能都是同樣的. 可是區別在於 生成器的方式產生的是一個惰性計算序列. 在調用時才進行計算得出下一個值. 而第二種常規函數的方式,是先計算得出全部結果返回一個列表. 從內存佔用的角度來講,生成器函數的方式更優一點.

生成器表達式

與列表解析式差很少. 生成器表達式用來構造一些邏輯相對簡單的生成器. 好比

g = (x**2 for x in range(4))

在使用時能夠經過next()函數或者for循環進行調用.

實戰:改寫map和zip函數

改寫map函數

一年級版本:

def mymap(func,*seqs):
res=[]
print(list(zip(*seqs)))
for args in zip(*seqs):
res.append(func(*args))
return res

二年級版本:

def mymap(func,*seqs):    
return [func(*args) for args in zip(*seqs)]

三年級版本:

def mymap(func,*seqs):
res=[]
for args in zip(*seqs):
yield func(*args)


print(list(mymap(abs,[-1,-2,1,2,3])))
print(list(mymap(pow,[1,2,3],[2,3,4,5])))

小學畢業班版本

def mymap(func,*seqs):
return (func(*args) for args in zip(*seqs))

改寫zip函數

一年級版本

def myzip(*seqs):
seqs = [list(S) for S in seqs]
print(seqs)
res = []
while all(seqs):
res.append(tuple(S.pop(0) for S in seqs))
return res


print(myzip('abc', 'xyz'))

知識點: all()函數和any()函數. all()函數,若是可迭代對象中的全部元素都爲True或者可迭代對象爲None. 則返回True. any()函數,可迭代對象中的任一元素爲True則返回True.,若是迭代器爲空,則返回False.

二年級版本

def myzip(*seqs):
seqs = [list(S) for S in seqs]
while all(seqs):
yield tuple(S.pop(0) for S in seqs)

print(list(myzip('abc', 'xyz')))

參考資料:

相關文章
相關標籤/搜索