python3使用迭代生成器實現減少內存占用
技術背景
在python編碼中for循環處理任務時,會將所有的待遍歷參量加載到內存中。其實這本沒有必要,因為這些參量很有可能是一次性使用的,甚至很多場景下這些參量是不需要同時存儲在內存中的,這時候就會用到本文所介紹的迭代生成器yield。
基本使用
首先我們用一個例子來演示一下迭代生成器yield的基本使用方法,這個例子的作用是構造一個函數用於生成一個平方數組。在普通的場景中我們一般會直接構造一個空的列表,然後將每一個計算結果填充到列表中,最後return列表即可,對應的是這裡的函數square_number。而另外一個函數square_number_yield則是為瞭演示yield而構造的函數,其使用語法跟return是一樣的,不同的是每次隻會返回一個值:
# test_yield.py def square_number(length): s = [] for i in range(length): s.append(i ** 2) return s def square_number_yield(length): for i in range(length): yield i ** 2 if __name__ == '__main__': length = 10 sn1 = square_number(length) sn2 = square_number_yield(length) for i in range(length): print (sn1[i], '\t', end='') print (next(sn2))
在main函數中我們對比瞭兩種方法執行的結果,打印在同一行上面,用end=”指令可以替代行末的換行符號,具體執行的結果如下所示:
[dechin@dechin-manjaro yield]$ python3 test_yield.py 0 0 1 1 4 4 9 9 16 16 25 25 36 36 49 49 64 64 81 81
可以看到兩種方法打印出來的結果是一樣的。也許有些場景下就是需要持久化的存儲函數中返回的結果,這一點用yield也是可以實現的,可以參考如下示例:
# test_yield.py def square_number(length): s = [] for i in range(length): s.append(i ** 2) return s def square_number_yield(length): for i in range(length): yield i ** 2 if __name__ == '__main__': length = 10 sn1 = square_number(length) sn2 = square_number_yield(length) sn3 = list(square_number_yield(length)) for i in range(length): print (sn1[i], '\t', end='') print (next(sn2), '\t', end='') print (sn3[i])
這裡使用的方法是直接將yield生成的對象轉化成list格式,或者用sn3 = [i for i in square_number_yield(length)]這種寫法也是可以的,在性能上應該差異不大。上述代碼的執行結果如下:
[dechin@dechin-manjaro yield]$ python3 test_yield.py 0 0 0 1 1 1 4 4 4 9 9 9 16 16 16 25 25 25 36 36 36 49 49 49 64 64 64 81 81 81
進階測試
在前面的章節中我們提到,使用yield可以節省程序的內存占用,這裡我們來測試一個100000大小的隨機數組的平方和計算。如果使用正常的邏輯,那麼寫出來的程序就是如下所示(關於python內存占用的追蹤方法,可以參考這一篇博客):
# square_sum.py import tracemalloc import time import numpy as np tracemalloc.start() start_time = time.time() ss_list = np.random.randn(100000) s = 0 for ss in ss_list: s += ss ** 2 end_time = time.time() print ('Time cost is: {}s'.format(end_time - start_time)) snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') for stat in top_stats[:5]: print (stat)
這個程序一方面通過time來測試執行的時間,另一方面利用tracemalloc追蹤程序的內存變化。這裡是先用np.random.randn()直接產生瞭100000個隨機數的數組用於計算,那麼自然在計算的過程中需要存儲這些生成的隨機數,就會占用這麼多的內存空間。如果使用yield的方法,每次隻產生一個用於計算的隨機數,並且按照上一個章節中的用法,這個迭代生成的隨機數也是可以轉化為一個完整的list的:
# yield_square_sum.py import tracemalloc import time import numpy as np tracemalloc.start() start_time = time.time() def ss_list(length): for i in range(length): yield np.random.random() s = 0 ss = ss_list(100000) for i in range(100000): s += next(ss) ** 2 end_time = time.time() print ('Time cost is: {}s'.format(end_time - start_time)) snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') for stat in top_stats[:5]: print (stat)
這兩個示例的執行結果如下,可以放在一起進行對比:
[dechin@dechin-manjaro yield]$ python3 square_sum.py Time cost is: 0.24723434448242188s square_sum.py:9: size=781 KiB, count=2, average=391 KiB square_sum.py:12: size=24 B, count=1, average=24 B square_sum.py:11: size=24 B, count=1, average=24 B [dechin@dechin-manjaro yield]$ python3 yield_square_sum.py Time cost is: 0.23023390769958496s yield_square_sum.py:9: size=136 B, count=1, average=136 B yield_square_sum.py:14: size=112 B, count=1, average=112 B yield_square_sum.py:11: size=79 B, count=2, average=40 B yield_square_sum.py:10: size=76 B, count=2, average=38 B yield_square_sum.py:15: size=28 B, count=1, average=28 B
經過比較我們發現,兩種方法的計算時間是幾乎差不多的,但是在內存占用上yield有著明顯的優勢。當然,也許這個例子並不是非常的恰當,但是本文主要還是介紹yield的使用方法及其應用場景。
無限長迭代器
在參考鏈接1中提到瞭一種用法是無限長的迭代器,比如按順序返回所有的素數,那麼此時我們如果用return來返回所有的元素並存儲到一個列表裡面,就是一個非常不經濟的辦法,所以可以使用yield來迭代生成,參考鏈接1中的源代碼如下所示:
def get_primes(number): while True: if is_prime(number): yield number number += 1
那麼類似的,這裡我們用while True可以展示一個簡單的案例——返回所有的偶數:
# yield_iter.py def yield_range2(i): while True: yield i i += 2 iter = yield_range2(0) for i in range(10): print (next(iter))
因為這裡我們限制瞭長度是10,所以最終會返回10個偶數:
[dechin@dechin-manjaro yield]$ python3 yield_iter.py
總結概要
本文介紹瞭python的迭代器yield,其實關於yield,我們可以簡單的將其理解為單個元素的return。這樣不僅就初步理解瞭yield的使用語法,也能夠大概瞭解到yield的優勢,也就是在計算過程中每次隻占用一個元素的內存,而不需要一直存儲大量的元素在內存中。
到此這篇關於python3使用迭代生成器實現減少內存占用的文章就介紹到這瞭,更多相關python3實現減少內存占用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Python中迭代器與生成器的用法
- Python編程itertools模塊處理可迭代集合相關函數
- 一文搞懂python 中的迭代器和生成器
- 正確理解python迭代器與生成器
- Python 生成器yield原理及用法