生成器和迭代器 列表生成式 現在有這麼一個需求,要將 這個列表中的每個元素都加1,那麼需要怎麼實現呢?你可能會想到這幾種方式: 其實還有一種寫法,如下: 通過列表生成式,我們可以直接創建一個列表。但是受到記憶體限制,列表容量肯定是有限制的,就像是遞歸,最大遞歸深度python就對其作了限制。而且,創建 ...
生成器和迭代器
列表生成式
現在有這麼一個需求,要將[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
這個列表中的每個元素都加1,那麼需要怎麼實現呢?你可能會想到這幾種方式:
a = list(range(0,10))
# print(a)
# 1.二逼青年版
b = []
for i in a:
b.append(i+1)
print(b)
# 2.普通青年版
c = a
for index,i in enumerate(c):
a[index] += 1
print(c)
# 3.文藝青年版
d = a
d = map(lambda x:x,d)
for i in d:
print(i)
其實還有一種寫法,如下:
# 4.裝逼青年版
e = a
e = [i+1 for i in range(10)]
print(e)
通過列表生成式,我們可以直接創建一個列表。但是受到記憶體限制,列表容量肯定是有限制的,就像是遞歸,最大遞歸深度python就對其作了限制。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅想訪問的是前幾個元素的話,那麼後面絕大多數元素占用的空間就是浪費了。
就比如說:我們家是賣手機的,我們找到了專門製作手機的廠商,告訴他,我這個手機要1000萬台,你們給我做吧,然而廠商考慮,和你說:這款手機我不知道能不能賣這麼多,你看我們就1000台分一批給你生產出來,如果你還要,那麼再給你生產1000台,你考慮到這也是一個問題,所以便同意了。
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈中不斷推算出後面的元素呢?這樣就不需要去創建一個完整的list了,從而節省了大量的空間,在python中,這樣一邊迴圈一邊計算的機制,稱為:生成器:generator
創建一個generator有很多種辦法,第一種方法很簡單,只需要把一個列表生成式的[]改成(),就創建了一個generator:
l = [x*x for x in range(10)]
print(l) # l = [x*x for x in range(10)]
g = (x*x for x in range(10))
print(g)
運行結果為:
<generator object <genexpr> at 0x000001E28508FFC0>
創建l和g的卻別僅在於最外層的[]和(),l是一個list,而g是一個generator
我們可以直接列印出來l列表中的每一個元素,但是怎麼列印出generator的每一個元素呢?
如果需要列印的話,我們就需要使用next()函數獲得generator的下一個返回值:
g = (x*x for x in range(10))
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
運行結果為:
<generator object <genexpr> at 0x000002586EF82518>
0
1
4
9
16
我們講過,generator保存的是演算法,每次調用next(g)後就能計算出g的下一個元素的值,直到計算出最後一個元素,沒有更多的元素時,會拋出StopIteration異常
當然,每需要獲取下一個元素的值,就需要使用一次next的話,就有點太變態了,所以,正確的方法時for迴圈,因為generator也是可迭代的
g = (x*x for x in range(10))
for i in g:
print(i)
運行結果為:
0
1
4
9
16
25
36
49
64
81
genera足夠強大,如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函數來實現,著名的斐波那契數列用列表生成式寫不出來,但是用函數把它列印出來卻很容易:
def fib(max):
n,a,b = 0,1,1 # 分別賦值
while n < max: # 條件判斷
print(b)
a,b = b,a+b # 賦值,把b賦值給a,把a+b賦值給b,在這裡需要註意的是,是相加之前的結果,不是把b賦值給a後,然後再相加
n += 1
return 'Done'
fib(10)
運行結果如下:
1
2
3
5
8
13
21
34
55
89
仔細觀察:可以看出,fib函數是定義了斐波那契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似generator。
也就是說,上面的函數和generator只有一步之遙。要把fib函數編程生成器generator,只需要把print(b)改成yield b就可以了:
def fib(max):
n,a,b = 0,1,1
while n < max:
yield b
# 出現了yield就相當於把這段程式變成了生成器
# yield把值返回到外部,而且不用終止
# yield把函數的執行過程凍結到這一步,並且把b的值返回給外面的next()
a,b = b,a+b
n += 1
return 'Done'
f = fib(10)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
運行結果如下
1
2
3
5
8
13
想要獲取下一個元素的值,用next就可以了
這裡最難理解的是generator和函數的執行順序流程不一樣。函數是順序執行,遇到return語句或者最後一行函數語句就返回(結束),而編程generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次被next()調用時從上次返回的yield語句出繼續執行。
def fib(max):
n,a,b = 0,1,1
while n < max:
yield b
a,b = b,a+b
n += 1
return 'Done'
data = fib(10)
print(data)
print(data.__next__())
print('乾點別的事')
print(data.__next__())
運行結果如下:
<generator object fib at 0x0000020D5B02FFC0>
1
乾點別的事
2
在上面的fib例子中,我們在迴圈過程中不斷調用yield,就會不斷中斷,當然要給迴圈設置一個條件來退出迴圈,不然就會產生一個無限數列出來。同樣的,把函數改成generator後,我們都是使用for迴圈來獲取返回值
def fib(max):
n,a,b = 0,1,1
while n < max:
yield b
a,b = b,a+b
n += 1
return 'Done'
data = fib(10)
for i in data:
print(i)
運行結果如下:
1
2
3
5
8
13
21
34
55
89
但是我們發現了,如果使用for迴圈的話,根本拿不到generator的return語句返回值。所以如果想拿到返回的值的話,必須要捕捉異常
def fib(max):
n,a,b = 0,1,1
while n < max:
yield b
a,b = b,a+b
n += 1
return 'Done'
data = fib(10)
while True:
try:
x = next(data)
print('data:',x)
except StopIteration as e:
print('Generator return value:',e.value)
break
運行結果:
data: 1
data: 2
data: 3
data: 5
data: 8
data: 13
data: 21
data: 34
data: 55
data: 89
迭代器
我們已經直到,可以直接作用域for迴圈的數據類型有以下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等
另一類是generator,包括生成器和帶yield 的 generator fgunction等
這些可以直接作用於for迴圈的對象統稱為可迭代對象:Iterable
可以使用isinstance()判斷一個對象是否是Iterstance對象:
In [1]: from collections import Iterable
In [2]: isinstance([],Iterable)
In [2]: isinstance([],Iterable)
Out[2]: True
In [3]: isinstance((),Iterable)
Out[3]: True
In [4]: isinstance({},Iterable)
Out[4]: True
In [6]: isinstance('abc',Iterable)
Out[6]: True
In [7]: isinstance((x for x in range(10)),Iterable)
Out[7]: True
而生成器不但可以作用域for迴圈,還可以被next()函數不斷調用並返回下一個值,直到拋出錯誤
可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator
可以使用isinstance()判斷一個對象是否是Iterator對象
In [1]: from collections import Iterator
In [2]: isinstance((),Iterator)
Out[2]: False
In [3]: isinstance([],Iterator)
Out[3]: False
In [4]: isinstance({},Iterator)
Out[4]: False
In [5]: isinstance((x for x in range(10)),Iterator)
Out[5]: True
生成器都是迭代器,但列表、元組、字典雖然是可迭代對象,但不是迭代器
把列表、元組、字典等可迭代對象轉換成迭代器可以使用iter()函數
In [6]: isinstance(iter([]),Iterator)
Out[6]: True
In [7]: isinstance(iter({}),Iterator)
Out[7]: True
你可能會問,為什麼List,tuole,dict等數據類型不是迭代器呢?
這是因為python的迭代器對象表示的是一個數據流,迭代器對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據的時候拋出異常。可以把這個數據流看作是一個有序序列,但我們卻不能提高知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以,Iterator的計算是惰性的,只需在返回下一個數據時它才會計算。
Iterator甚至可以表示一個無限大的數據流,而list時永遠不可能存儲全體自然數的。