Python 3.3 為改進代碼中的異常處理所做的工作
探索異常處理和其他未被充分利用但仍然有用的 Python 特性。
這是 Python 3.x 首發特性系列文章的第四篇。Python 3.3 于 2012 年首次發布,盡管它已經發布了很長時間,但它引入的許多特性都沒有得到充分利用,而且相當酷。下面是其中的三個。
yield from
yield 關鍵字使 Python 更加強大。可以預見的是,人們都開始使用它來創建整個迭代器的生態系統。itertools 模塊和 more-itertools PyPI 包就是其中兩個例子。
有時,一個新的生成器會想要使用一個現有的生成器。作為一個簡單的(盡管有點故意設計)的例子,設想你想枚舉所有的自然數對。
一種方法是按照“自然數對的和,自然數對的第一項”的順序生成所有的自然數對。用 yield from 來實現這個方法是很自然的。
yield from <x> 關鍵字是以下的簡稱:
for item in x:yield item
import itertoolsdef pairs():for n in itertools.count():yield from ((i, n-i) for i in range(n+1))
list(itertools.islice(pairs(), 6))
[(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0)]
隱式命名空間包
假設有一個叫 Parasol 的虛構公司,它制造了一堆東西。它的大部分內部軟件都是用 Python 編寫的。雖然 Parasol 已經開源了它的一些代碼,但其中一些代碼對于開源來說過于專有或專業。
該公司使用內部 DevPI 服務器來管理內部軟件包。對于 Parasol 的每個 Python 程序員來說,在 PyPI 上找一個未使用的名字是沒有意義的,所以所有的內部包都被稱為 parasol.<business division>.<project>。遵守最佳實踐,開發人員希望包的名字能反映出這個命名系統。
這一點很重要!如果 parasol.accounting.numeric_tricks 包安裝了一個名為 numeric_tricks 的頂層模塊,這意味著依賴這個包的人將無法使用名為 numeric_tricks 的 PyPI 包,不管它寫的有多好。
然而,這給開發者留下了一個兩難的選擇:哪個包擁有 parasol/__init__.py 文件?從 Python 3.3 開始,最好的解決辦法是把 parasol,可能還有 parasol.accounting,變成沒有 __init__.py 文件的 命名空間包。
抑制異常的上下文
有時,在從異常中恢復的過程中出現的異常是一個問題,有上下文來跟蹤它是很有用的。然而,有時卻不是這樣:異常已經被處理了,而新的情況是一個不同的錯誤狀況。
例如,想象一下,在字典中查找一個鍵失敗后,如果不能分析它,則希望失敗并返回 ValueError()。
import timedef expensive_analysis(data):time.sleep(10)if data[0:1] == ">":return data[1:]return None
這個函數需要很長的時間,所以當你使用它時,想要對結果進行緩存:
cache = {}def last_letter_analyzed(data):try:analyzed = cache[data]except KeyError:analyzed = expensive_analysis(data)if analyzed is None:raise ValueError("invalid data", data)cached[data] = analyzedreturn analyzed[-1]
不幸的是,當出現緩存沒有命中時,回溯看起來很難看:
last_letter_analyzed("stuff")
---------------------------------------------------------------------------KeyError Traceback (most recent call last)<ipython-input-16-a525ae35267b> in last_letter_analyzed(data)4 try:----> 5 analyzed = cache[data]6 except KeyError:KeyError: 'stuff'
在處理上述異常的過程中,發生了另一個異常:
ValueError Traceback (most recent call last)<ipython-input-17-40dab921f9a9> in <module>----> 1 last_letter_analyzed("stuff")<ipython-input-16-a525ae35267b> in last_letter_analyzed(data)7 analyzed = expensive_analysis(data)8 if analyzed is None:----> 9 raise ValueError("invalid data", data)10 cached[data] = analyzed11 return analyzed[-1]ValueError: ('invalid data', 'stuff')
如果你使用 raise ... from None,你可以得到更多可讀的回溯:
def last_letter_analyzed(data):try:analyzed = cache[data]except KeyError:analyzed = expensive_analysis(data)if analyzed is None:raise ValueError("invalid data", data) from Nonecached[data] = analyzedreturn analyzed[-1]
last_letter_analyzed("stuff")
---------------------------------------------------------------------------ValueError Traceback (most recent call last)<ipython-input-21-40dab921f9a9> in <module>----> 1 last_letter_analyzed("stuff")<ipython-input-20-5691e33edfbc> in last_letter_analyzed(data)5 analyzed = expensive_analysis(data)6 if analyzed is None:----> 7 raise ValueError("invalid data", data) from None8 cached[data] = analyzed9 return analyzed[-1]ValueError: ('invalid data', 'stuff')
歡迎來到 2012 年
盡管 Python 3.3 在十年前就已經發布了,但它的許多功能仍然很酷,而且沒有得到充分利用。如果你還沒有,就把它們添加到你的工具箱中吧。
























