在Python 3中緩存Exception對(duì)象會(huì)造成什么后果?
??Python 3有一個(gè)不太容易被注意到的改進(jìn):異常對(duì)象現(xiàn)在有了一個(gè)新的屬性__traceback__。
這個(gè)屬性自動(dòng)保存了traceback
列表,當(dāng)每次這個(gè)異常被重新raise
出來的時(shí)候,會(huì)自動(dòng)在__traceback__
中追加一條記錄。這個(gè)功能對(duì)于異步編程來說非常有幫助:在另一個(gè)線程或者協(xié)程中拋出的異常,被捕獲、傳輸?shù)狡渌胤?,再重新拋出來的時(shí)候,不僅最初的traceback
得以保留,每次被重新拋出的記錄也都會(huì)保留下來,這樣異常的traceback
就可以提供很細(xì)致的信息。
說完了好處,再來說這個(gè)新功能導(dǎo)致的問題:exception
對(duì)象現(xiàn)在是一個(gè)可變的對(duì)象了,每次raise
都會(huì)修改這個(gè)對(duì)象。如果將一個(gè)exception
對(duì)象拋出多次,就會(huì)保留每次拋出的traceback,可能導(dǎo)致的結(jié)果包括:錯(cuò)誤的堆棧信息;意外地破壞了運(yùn)行數(shù)據(jù);內(nèi)存泄漏等等。
舉例:
堆棧信息錯(cuò)誤很好理解,舉一個(gè)意外破壞運(yùn)行數(shù)據(jù)的例子:
import asyncio import unittest async def it(raise_, ignore=True): yield 1 if not ignore: await asyncio.sleep(0.01) try: raise ValueError('test2') from raise_ except Exception: import traceback traceback.print_exc() if ignore: pass else: raise await asyncio.sleep(0.1) yield 2 class MyTest(unittest.TestCase): def test(self): try: raise ValueError('testerror') except ValueError as e: exc = e async def task1(): with self.assertRaises(ValueError): async for i in it(exc, False): print("task1: ", i) async def task2(): async for i in it(exc, True): print("task2: ", i) async def main(): t1 = asyncio.ensure_future(task1()) t2 = asyncio.ensure_future(task2()) await t1 await t2 asyncio.get_event_loop().run_until_complete(main()) if __name__ == '__main__': unittest.main()
運(yùn)行這段代碼,你會(huì)很驚訝地發(fā)現(xiàn)本來應(yīng)該很快結(jié)束的程序居然卡住了。原因在于assertRaises
這個(gè)unittest
庫中的函數(shù),為了在保存單元測(cè)試結(jié)果的過程中不要占用太多內(nèi)存,所以在保存異常時(shí)強(qiáng)制清空了異常堆棧中的locals
變量。然而因?yàn)檫@個(gè)異常同時(shí)在兩個(gè)Task
中被使用,assertRaises
捕獲到的異常堆棧中,還有尚未退出的協(xié)程的堆棧,清空了這個(gè)協(xié)程的堆棧會(huì)導(dǎo)致這個(gè)協(xié)程沒有辦法繼續(xù)正常執(zhí)行下去,進(jìn)一步導(dǎo)致相應(yīng)的Future
沒有人設(shè)置,等待這個(gè)Future
的過程就無法正常結(jié)束了。
再舉一個(gè)內(nèi)存泄漏的例子:這個(gè)例子不在用戶代碼里,而在PyPy3 6.0版本的解釋器里。在PyPy3中,為了提高運(yùn)行速度,關(guān)閉一個(gè)生成器使用的GeneratorExit對(duì)象被解釋器緩存了起來,這導(dǎo)致每次調(diào)用生成器的close
方法時(shí),當(dāng)前close的生成器的frame
都被保存到了這個(gè)global對(duì)象里,導(dǎo)致了生成器對(duì)象和frame對(duì)象都無法被GC回收,產(chǎn)生了嚴(yán)重的內(nèi)存泄漏。
下面這段代碼在PyPy 3中會(huì)迅速耗盡系統(tǒng)內(nèi)存,而在CPython 3中則沒有問題:
def test(): yield 1 while True: t = test() t.close()
結(jié)論:
在Python 3中,最佳實(shí)踐是:
- 永遠(yuǎn)不要持久保存一個(gè)已經(jīng)被拋出過的
Exception
對(duì)象 - 每個(gè)被捕獲的
Exception
對(duì)象,至多被重新raise一次(不管經(jīng)過怎樣的過程) - 在需要將同一個(gè)異常廣播到多個(gè)過程時(shí)(例如:多個(gè)過程等待了同一個(gè)異步過程),最好每次都重新復(fù)制整個(gè)
Exception
對(duì)象,或者為每個(gè)過程創(chuàng)建一個(gè)新的Exception
對(duì)象。通過Python 3的新語法raise ... from ...,可以在新的Exception
中保留老的traceback。
第三點(diǎn)的一個(gè)特例是asyncio
中的Future對(duì)象,如果一個(gè)Future被await了多次,而這個(gè)Future拋出了異常,就會(huì)出現(xiàn)第三種情形,此時(shí)堆棧信息可能會(huì)混亂。
到此這篇關(guān)于在Python 3中緩存Exception對(duì)象會(huì)造成什么后果?的文章就介紹到這了,更多相關(guān)不要在Python 3中緩存Exception對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python連接Hadoop數(shù)據(jù)中遇到的各種坑(匯總)
這篇文章主要介紹了Python連接Hadoop數(shù)據(jù)中遇到的各種坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04基于Python編寫一個(gè)根據(jù)姓名測(cè)性別的小程序
這篇文章主要為大家介紹了如何利用Python編寫一款根據(jù)中文名能猜測(cè)性別的一款界面化的小程序,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-03-03python opencv鼠標(biāo)畫點(diǎn)之cv2.drawMarker()函數(shù)
這篇文章主要給大家介紹了關(guān)于python opencv鼠標(biāo)畫點(diǎn)之cv2.drawMarker()函數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用opencv具有一定的參考下學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-10-10Python網(wǎng)絡(luò)編程之ZeroMQ知識(shí)總結(jié)
這篇文章主要介紹了Python網(wǎng)絡(luò)編程之ZeroMQ知識(shí)總結(jié),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)python的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04Python實(shí)現(xiàn)批量繪制遙感影像數(shù)據(jù)的直方圖
這篇文章主要為大家詳細(xì)介紹了如何基于Python中g(shù)dal模塊,實(shí)現(xiàn)對(duì)大量柵格圖像批量繪制直方圖,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-02-02使用Python將xmind腦圖轉(zhuǎn)成excel用例的實(shí)現(xiàn)代碼(一)
這篇文章主要介紹了使用Python將xmind腦圖轉(zhuǎn)成excel用例的實(shí)現(xiàn)代碼(一),本文給大家介紹的非常詳細(xì)對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10