初學Python——列表生成式、生成器和叠代器
一、列表生成式
假如現在有這樣一個需求:快速生成一個列表[1,2,3,4,5,6,7,8,9,10],該如何實現?
在不知道列表生成式的情況下,可能會這樣寫:
a=[1,2,3,4,5,6,7,8,9,10]
如果要每個值+1呢?可能會這樣:
for index,i in enumerate(a):
a[index] +=1
print(a)
不夠方便,這裏講一個快速生成列表的方法:列表生成式。意思就是立即生成列表。
生成一個1到10的列表:
a = [i+1 for i in range(10)] print( a) # output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成一個2~20的偶數列表:
a=[ i*2 for i in rang(1,11)]
print(a)
# output:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
它相當於:
a=[]
for i in range(1,11): #列表生成式
a.append(i*2)
print(a)
生成的列表已經存在在內存中。
二、生成器
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的列表,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個生成器,只需要把列表生成式中的 [ ] 改成 ( ) 即可。
b=[i*2 for i in rang(10)] # 列表生成式 print(b) c=( i*2 for i in range(10) ) #生成器 print(c) # output: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] <generator object <genexpr> at 0x000001D0089B45C8>
輸出c,得到的是數據類型說明和它的內存地址。
生成器只是名義上生成一個列表,但實際上卻沒有占用那麽大內存,生成器只有調用的時候才會生成相應的數據。
如果要打印生成器的數據,則需要.__next__()方法
print(c.__next__()) # 輸出第一個數
0
print(c.__next__()) # 輸出第二個數
1
print(c.__next__()) # 輸出第三個數
2
print(c.__next__()) # 輸出第四個數
3
print(c.__next__()) # 輸出第五個數
4
如果我只需要當中的最後一個數據呢?能不能直接輸出?
抱歉,不能。而且,生成器的數據只能從前往後去訪問,不能從後往前去訪問,在內存中只保留一個值,也就是說,訪問過的數據已經無法再次訪問。
如果生成器有很多的數據,要全部輸出,有沒有簡便的寫法?
抱歉,沒有,您只能一個一個地輸出。
當然,像上面那樣不斷調用.__next__()還是太坑爹了,可以用for去叠代它(生成器也是可叠代對象):
g = (x * x for x in range(10))
for n in g:
print(n)
那,,我還要生成器有卵用??
還是有點卵用的,生成器一般依托於函數實現,比如,我先定義一個函數fib(),函數內定義了數列的推算規則
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return ‘done‘
# 註釋:
a, b = b, a + b 相當於:
t = (b, a + b) # t是一個tuple
a = t[0]
b = t[1]
它不必寫出顯式變量 t
如果給fib()傳參10,它將輸出一連串的數字,可以組成一個數列:
1,1,2,3,5,8,13,21,34,55
此時的fib函數,已經非常接近生成器了,只需要一個yield即可,
def fib(max): #當函數中有yield出現時,不能將其簡單視為函數,是一個生成器。
"生成器"
n,a,b=0,0,1
while n<max:
yield b #yield保存了函數當前的中斷狀態,返回當前b的值
a,b=b,a+b
n=n+1 #計數器
return "done"
此時,fib(10)是一個生成器,
f = fib(6)
print(f)
<generator object fib at 0x104feaaa0>
這裏最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return
語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()
的時候執行,遇到yield
語句返回,再次執行時從上次返回的yield
語句處繼續執行。
來一個一個地輸出它的值:
f=fib(10) print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__())
因為只能一個一個地輸出,且不能得知長度,所以總會有越界的時候,會報一個異常StopIteration,導致程序停止
所以需要捕獲異常:
while 1:
try: #如果沒有出現異常,執行下面語句
x=next(g)
print("g:",x)
except StopIteration as e: #如果出現異常StopIteration,把它賦給e,執行下面的語句
print(e.value)
break
前面講到,生成器只能一個一個地取出數據,在fib函數執行過程中會中斷,為什麽要這樣呢?有什麽用嗎?
它厲害在:可以在單線程的情況下實現並發效果,舉個例子:
import time def custumer(name): print("{0}準備來吃包子了".format(name)) while 1: baozi = yield #每次運行到這一行時都會中斷 print("包子{0}來了,被{1}吃掉了".format(baozi,name)) def producer(name): c1=custumer("老大") c2=custumer("老二") c1.__next__() c2.__next__() # next 只是在調用yield print("{0}開始做包子啦!".format(name)) for i in range(1,15,2): time.sleep(1) print("做了兩個包子") c1.send(i) # send 調用yield的同時給它傳值 c2.send(i+1) producer("alex")View Code
如果在自己的解釋器上執行,會發現一個程序有三個任務交錯切換運行,看上去就像三個任務同時在進行。
三、叠代器
我們已經知道,可以直接作用於for
循環的數據類型有以下幾種:
一類是集合數據類型,如list
、tuple
、dict
、set
、str
等;
一類是generator
,包括生成器和帶yield
的generator function。
這些可以直接作用於for
循環的對象統稱為可叠代對象:Iterable
。
可以使用isinstance()
判斷一個對象是否是Iterable
對象。
for循環本質上時不斷調用next()函數實現的:
a=[1,2,3,4,5]
for x in a:
print(x)
#完全等價於
it=iter(a) # 將列表轉化成叠代器對象
while 1:
try:
x=next(it) #獲得下一個值
print(x)
except StopIteration:
break #遇到StopIteration異常就跳出循環
在文件操作時,
for line in f:
print(line)
每次輸出其實都是調用next()函數,在Python3中已經看不出是一個叠代器了。
初學Python——列表生成式、生成器和叠代器