python3使用迭代生成器實(shí)現(xiàn)減少內(nèi)存占用
技術(shù)背景
在python編碼中for循環(huán)處理任務(wù)時(shí),會(huì)將所有的待遍歷參量加載到內(nèi)存中。其實(shí)這本沒(méi)有必要,因?yàn)檫@些參量很有可能是一次性使用的,甚至很多場(chǎng)景下這些參量是不需要同時(shí)存儲(chǔ)在內(nèi)存中的,這時(shí)候就會(huì)用到本文所介紹的迭代生成器yield。
基本使用
首先我們用一個(gè)例子來(lái)演示一下迭代生成器yield的基本使用方法,這個(gè)例子的作用是構(gòu)造一個(gè)函數(shù)用于生成一個(gè)平方數(shù)組。在普通的場(chǎng)景中我們一般會(huì)直接構(gòu)造一個(gè)空的列表,然后將每一個(gè)計(jì)算結(jié)果填充到列表中,最后return列表即可,對(duì)應(yīng)的是這里的函數(shù)square_number。而另外一個(gè)函數(shù)square_number_yield則是為了演示yield而構(gòu)造的函數(shù),其使用語(yǔ)法跟return是一樣的,不同的是每次只會(huì)返回一個(gè)值:
# 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函數(shù)中我們對(duì)比了兩種方法執(zhí)行的結(jié)果,打印在同一行上面,用end=''指令可以替代行末的換行符號(hào),具體執(zhí)行的結(jié)果如下所示:
[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
可以看到兩種方法打印出來(lái)的結(jié)果是一樣的。也許有些場(chǎng)景下就是需要持久化的存儲(chǔ)函數(shù)中返回的結(jié)果,這一點(diǎn)用yield也是可以實(shí)現(xiàn)的,可以參考如下示例:
# 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生成的對(duì)象轉(zhuǎn)化成list格式,或者用sn3 = [i for i in square_number_yield(length)]這種寫法也是可以的,在性能上應(yīng)該差異不大。上述代碼的執(zhí)行結(jié)果如下:
[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
進(jìn)階測(cè)試
在前面的章節(jié)中我們提到,使用yield可以節(jié)省程序的內(nèi)存占用,這里我們來(lái)測(cè)試一個(gè)100000大小的隨機(jī)數(shù)組的平方和計(jì)算。如果使用正常的邏輯,那么寫出來(lái)的程序就是如下所示(關(guān)于python內(nèi)存占用的追蹤方法,可以參考這一篇博客):
# 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)
這個(gè)程序一方面通過(guò)time來(lái)測(cè)試執(zhí)行的時(shí)間,另一方面利用tracemalloc追蹤程序的內(nèi)存變化。這里是先用np.random.randn()直接產(chǎn)生了100000個(gè)隨機(jī)數(shù)的數(shù)組用于計(jì)算,那么自然在計(jì)算的過(guò)程中需要存儲(chǔ)這些生成的隨機(jī)數(shù),就會(huì)占用這么多的內(nèi)存空間。如果使用yield的方法,每次只產(chǎn)生一個(gè)用于計(jì)算的隨機(jī)數(shù),并且按照上一個(gè)章節(jié)中的用法,這個(gè)迭代生成的隨機(jī)數(shù)也是可以轉(zhuǎn)化為一個(gè)完整的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)
這兩個(gè)示例的執(zhí)行結(jié)果如下,可以放在一起進(jìn)行對(duì)比:
[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
經(jīng)過(guò)比較我們發(fā)現(xiàn),兩種方法的計(jì)算時(shí)間是幾乎差不多的,但是在內(nèi)存占用上yield有著明顯的優(yōu)勢(shì)。當(dāng)然,也許這個(gè)例子并不是非常的恰當(dāng),但是本文主要還是介紹yield的使用方法及其應(yīng)用場(chǎng)景。
無(wú)限長(zhǎng)迭代器
在參考鏈接1中提到了一種用法是無(wú)限長(zhǎng)的迭代器,比如按順序返回所有的素?cái)?shù),那么此時(shí)我們?nèi)绻胷eturn來(lái)返回所有的元素并存儲(chǔ)到一個(gè)列表里面,就是一個(gè)非常不經(jīng)濟(jì)的辦法,所以可以使用yield來(lái)迭代生成,參考鏈接1中的源代碼如下所示:
def get_primes(number): while True: if is_prime(number): yield number number += 1
那么類似的,這里我們用while True可以展示一個(gè)簡(jiǎn)單的案例——返回所有的偶數(shù):
# 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))
因?yàn)檫@里我們限制了長(zhǎng)度是10,所以最終會(huì)返回10個(gè)偶數(shù):
[dechin@dechin-manjaro yield]$ python3 yield_iter.py
總結(jié)概要
本文介紹了python的迭代器yield,其實(shí)關(guān)于yield,我們可以簡(jiǎn)單的將其理解為單個(gè)元素的return。這樣不僅就初步理解了yield的使用語(yǔ)法,也能夠大概了解到y(tǒng)ield的優(yōu)勢(shì),也就是在計(jì)算過(guò)程中每次只占用一個(gè)元素的內(nèi)存,而不需要一直存儲(chǔ)大量的元素在內(nèi)存中。
到此這篇關(guān)于python3使用迭代生成器實(shí)現(xiàn)減少內(nèi)存占用的文章就介紹到這了,更多相關(guān)python3實(shí)現(xiàn)減少內(nèi)存占用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 模擬貸款卡號(hào)生成規(guī)則過(guò)程解析
這篇文章主要介紹了python 模擬貸款卡號(hào)生成規(guī)則過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08python tkinter中的錨點(diǎn)(anchor)問(wèn)題及處理
這篇文章主要介紹了python tkinter中的錨點(diǎn)(anchor)問(wèn)題及處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06使用Python中tkinter庫(kù)簡(jiǎn)單gui界面制作及打包成exe的操作方法(二)
這篇文章主要介紹了使用Python中tkinter庫(kù)簡(jiǎn)單gui界面制作及打包成exe的操作方法(二),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10如何處理Python3.4 使用pymssql 亂碼問(wèn)題
這篇文章主要介紹了如何處理Python3.4 使用pymssql 亂碼問(wèn)題的相關(guān)資料,涉及到python pymssql相關(guān)知識(shí),對(duì)此感興趣的朋友一起學(xué)習(xí)吧2016-01-01基于Python實(shí)現(xiàn)下載網(wǎng)易音樂(lè)代碼實(shí)例
這篇文章主要介紹了基于Python實(shí)現(xiàn)下載網(wǎng)易音樂(lè)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08利用keras加載訓(xùn)練好的.H5文件,并實(shí)現(xiàn)預(yù)測(cè)圖片
今天小編就為大家分享一篇利用keras加載訓(xùn)練好的.H5文件,并實(shí)現(xiàn)預(yù)測(cè)圖片,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01python實(shí)現(xiàn)切割url得到域名、協(xié)議、主機(jī)名等各個(gè)字段的例子
今天小編就為大家分享一篇python實(shí)現(xiàn)切割url得到域名、協(xié)議、主機(jī)名等各個(gè)字段的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07使用pytorch加載并讀取COCO數(shù)據(jù)集的詳細(xì)操作
這篇文章主要介紹了使用pytorch加載并讀取COCO數(shù)據(jù)集,基礎(chǔ)知識(shí)包括元祖、字典、數(shù)組,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05