PYthon的生成器

大家還記得上次講到的PYthon的迭代器嗎,今天我們要講一個和它相關的,生成器。

通過列表生成式,我們可以直接創建一個列表,但是,受到內存限制,列表容量肯定是有限的,而且創建一個包含100萬個元素的列表,不僅佔用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。我們先來看一個簡單的列表生成器對比例子:

有一個需求,列表 [0,1,2,3,4,5,6,7,8,9],要求把列表裡面的每個值加1,有兩種辦法:

*第一種:

PYthon的生成器

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = []
# for index,i in enumerate(info):
# print(i+1)
# b.append(i+1)
# print(b)
for index,i in enumerate(info):
info[index] +=1
print(info)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

*第二種:

PYthon的生成器

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a = [i+1 for i in range(10)]
print(a)

可以看到第二種比第一種要簡簡潔很多。

所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間,在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator

生成器是一個特殊的程序,可以被用作控制循環的迭代行為,python中生成器是迭代器的一種,使用yield返回值函數,每次調用yield會暫停,而可以使用next()函數和send()函數恢復生成器。

生成器類似於返回值為數組的一個函數,這個函數可以接受參數,可以被調用,但是,不同於一般的函數會一次性返回包括了所有數值的數組,生成器一次只能產生一個值,這樣消耗的內存數量將大大減小,而且允許調用函數可以很快的處理前幾個返回值,因此生成器看起來像是一個函數,但是表現得卻像是迭代器。

1generator

要創建一個generator,有很多種方法,第一種方法很簡單,把一個列表生成式的[]中括號改為()小括號,就可以創建一個generator

# 列表生成式

lis = [x * x for x in range(10)]

print(lis)

# 生成器

generator_ex = (x * x for x in range(10))

print(generator_ex)

#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

#<generator> at 0x0375FA70>/<generator>

PYthon的生成器

此處我們仔細看一下生成結果的兩行對比,創建lis和generator_ex除了表面的[]與()的區別外,生成的一個是列表,一個則是<generator> at 0x0375FA70>。如果需要打印generator_ex 的每一個值的話:/<generator>

generator_ex = (x*x for x in range(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
#結果:
0
1
4
9
16
25
36
49
64
81
Traceback (most recent call last):
File "D:\\\untitled\venv\lib\site-packages\IPyth on\core\interactiveshell.py", line 2869, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-5-942fb3d5fc8e>", line 12, in <module>
print(next(generator_ex))
StopIteration
/<module>/<ipython-input-5-942fb3d5fc8e>
PYthon的生成器

generator保存的是算法,每次調用next(generaotr_ex)就計算出它的下一個元素的值,直到計算出最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤,而且上面這樣不斷調用是一個不好的習慣,正確的方法是使用for循環,因為generator也是可迭代對象:

#生成器
generator_ex = (x*x for x in range(10))
for i in generator_ex:
print(i)
PYthon的生成器

所以我們創建一個generator後,基本上永遠不會調用next(),而是通過for循環來迭代,並且不需要關心StopIteration的錯誤,generator非常強大,如果推算的算法比較複雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現。

2生成器函數

為什麼叫生成器函數?因為它隨著時間的推移生成了一個數值隊列。一般的函數在執行完畢之後會返回一個值然後退出,但是生成器函數會自動掛起,然後重新拾起急需執行,它會利用yield關鍵字關起函數,給調用者返回一個值,同時保留了當前的足夠多的狀態,可以使函數繼續執行,生成器和迭代協議是密切相關的,可迭代的對象都有一個__next__()__成員方法,這個方法一種可返回迭代的下一項,或是引起異常結束迭代。

def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1
return 'done'
g = fib(6)
while True:
try:
x = next(g)
print('generator: ',x)
except StopIteration as e:
print("生成器返回值:",e.value)
break
#結果
generator: 1
generator: 1
generator: 2
generator: 3
generator: 5
generator: 8
生成器返回值: done

PYthon的生成器

這裡使用yield生成器使之打印結果不佔內存了。generator和函數的執行流程,函數是順序執行的,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次被next()調用時候從上次的返回yield語句處急需執行,也就是用多少,取多少,不佔內存。可是若只是如此操作也有一個弊端,在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來,而要規避這個弊端的話我們可以採取for循環的方式,可是為什麼最終使用的確實while呢?因為使用for循環語句,我們就無法獲取generator的return語句返回的值,因此就會報錯,產生上文中出現過的StopIteration錯誤,要獲取到返回值必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中,即採用上文中語句。

下面再來看一個生成器函數:

# 函數有了yield之後,函數名+()就變成了生成器
# return在生成器中代表生成器的中止,直接報錯
# next的作用是喚醒並繼續執行
# send的作用是喚醒並繼續執行,發送一個信息到生成器內部
def create_counter(n):
print("create_counter")
while True:
yield n
print("increment n")
n += 1
gen = create_counter(2)
print(gen)
print(next(gen))
print(next(gen))
#生成結果
<generator>
create_counter
2
increment n
3
/<generator>
PYthon的生成器


分享到:


相關文章: