Python 3.3 為改進(jìn)代碼中的異常處理所做的工作
探索異常處理和其他未被充分利用但仍然有用的 Python 特性。
這是 Python 3.x 首發(fā)特性系列文章的第四篇。Python 3.3 于 2012 年首次發(fā)布,盡管它已經(jīng)發(fā)布了很長(zhǎng)時(shí)間,但它引入的許多特性都沒(méi)有得到充分利用,而且相當(dāng)酷。下面是其中的三個(gè)。
yield from
yield
關(guān)鍵字使 Python 更加強(qiáng)大。可以預(yù)見(jiàn)的是,人們都開(kāi)始使用它來(lái)創(chuàng)建整個(gè)迭代器的生態(tài)系統(tǒng)。itertools 模塊和 more-itertools PyPI 包就是其中兩個(gè)例子。
有時(shí),一個(gè)新的生成器會(huì)想要使用一個(gè)現(xiàn)有的生成器。作為一個(gè)簡(jiǎn)單的(盡管有點(diǎn)故意設(shè)計(jì))的例子,設(shè)想你想枚舉所有的自然數(shù)對(duì)。
一種方法是按照“自然數(shù)對(duì)的和,自然數(shù)對(duì)的第一項(xiàng)”的順序生成所有的自然數(shù)對(duì)。用 yield from
來(lái)實(shí)現(xiàn)這個(gè)方法是很自然的。
yield from <x>
關(guān)鍵字是以下的簡(jiǎn)稱(chēng):
for item in x:
yield item
import itertools
def 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)]
隱式命名空間包
假設(shè)有一個(gè)叫 Parasol 的虛構(gòu)公司,它制造了一堆東西。它的大部分內(nèi)部軟件都是用 Python 編寫(xiě)的。雖然 Parasol 已經(jīng)開(kāi)源了它的一些代碼,但其中一些代碼對(duì)于開(kāi)源來(lái)說(shuō)過(guò)于專(zhuān)有或?qū)I(yè)。
該公司使用內(nèi)部 DevPI 服務(wù)器來(lái)管理內(nèi)部軟件包。對(duì)于 Parasol 的每個(gè) Python 程序員來(lái)說(shuō),在 PyPI 上找一個(gè)未使用的名字是沒(méi)有意義的,所以所有的內(nèi)部包都被稱(chēng)為 parasol.<business division>.<project>
。遵守最佳實(shí)踐,開(kāi)發(fā)人員希望包的名字能反映出這個(gè)命名系統(tǒng)。
這一點(diǎn)很重要!如果 parasol.accounting.numeric_tricks
包安裝了一個(gè)名為 numeric_tricks
的頂層模塊,這意味著依賴(lài)這個(gè)包的人將無(wú)法使用名為 numeric_tricks
的 PyPI 包,不管它寫(xiě)的有多好。
然而,這給開(kāi)發(fā)者留下了一個(gè)兩難的選擇:哪個(gè)包擁有 parasol/__init__.py
文件?從 Python 3.3 開(kāi)始,最好的解決辦法是把 parasol
,可能還有 parasol.accounting
,變成沒(méi)有 __init__.py
文件的 命名空間包。
抑制異常的上下文
有時(shí),在從異常中恢復(fù)的過(guò)程中出現(xiàn)的異常是一個(gè)問(wèn)題,有上下文來(lái)跟蹤它是很有用的。然而,有時(shí)卻不是這樣:異常已經(jīng)被處理了,而新的情況是一個(gè)不同的錯(cuò)誤狀況。
例如,想象一下,在字典中查找一個(gè)鍵失敗后,如果不能分析它,則希望失敗并返回 ValueError()
。
import time
def expensive_analysis(data):
time.sleep(10)
if data[0:1] == ">":
return data[1:]
return None
這個(gè)函數(shù)需要很長(zhǎng)的時(shí)間,所以當(dāng)你使用它時(shí),想要對(duì)結(jié)果進(jìn)行緩存:
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] = analyzed
return analyzed[-1]
不幸的是,當(dāng)出現(xiàn)緩存沒(méi)有命中時(shí),回溯看起來(lái)很難看:
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'
在處理上述異常的過(guò)程中,發(fā)生了另一個(gè)異常:
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] = analyzed
11 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 None
cached[data] = analyzed
return 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 None
8 cached[data] = analyzed
9 return analyzed[-1]
ValueError: ('invalid data', 'stuff')
歡迎來(lái)到 2012 年
盡管 Python 3.3 在十年前就已經(jīng)發(fā)布了,但它的許多功能仍然很酷,而且沒(méi)有得到充分利用。如果你還沒(méi)有,就把它們添加到你的工具箱中吧。