1. 程式人生 > >初學Python——列表生成式、生成器和叠代器

初學Python——列表生成式、生成器和叠代器

過程 100萬 import 通過 str __next__ 出現 tor 創建

一、列表生成式

假如現在有這樣一個需求:快速生成一個列表[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循環的數據類型有以下幾種:

一類是集合數據類型,如listtupledictsetstr等;

一類是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——列表生成式、生成器和叠代器