最近壓力比較大,讓我出賣一下我的同事。
我的同事 L 最近要用 Python 寫一個函式 lookup()。它會拿三個參數:
d:一個str到set的dictx:當作d的鍵值的字串y:字串或是None
它的功能是:
- 如果
d沒有x,則lookup()應回傳False。 - 如果
d有x且y是None,則lookup()應回傳 True。 - 如果
d有x且y不是None,則lookup()應回傳y是不是在d[x]之內。
這看起來很簡單,所以 L 就寫出以下的程式碼:
def lookup(d, x, y):
try:
s = d[x]
except KeyError:
return False
if y is None:
return True
return y in s
看起來很合理。但是過幾天後,同事 L 發現這個程式的結果有問題。不過這份 code 看起來很完美,不可能有問題呀。
一個小時過後,同事 L 突然頓悟了:問題不在於 lookup() 而是在於 d 的型別。因為在建構 d 的時候,同事 L 是使用 collections.defaultdict(set) 作為型別:
def build_table(lines):
d = collections.defaultdict(set)
for line in lines:
x, y = line.split(',', 1)
d[x].add(y)
return d
原本 lookup() 函式中的 d[x] 會馬上在 d 裡面加上一個空的 set 物件,然後回傳該 set 物件,所以 KeyError 例外永遠不會被拋出。
如果把上面 lookup() 的 d[x] 改為 d.get(x) 就沒問題了:
def lookup(d, x, y):
s = d.get(x)
if s is None:
return False
if y is None:
return True
return y in s
另一個方法是在 build_table() 回傳之前,把 defaultdict 複製成 dict:
def build_table(lines):
d = collections.defaultdict(set)
for line in lines:
x, y = line.split(',', 1)
d[x].add(y)
return dict(d)
或者,乾脆不要使用 defaultdict:
def build_table(lines):
d = {}
for line in lines:
x, y = line.split(',', 1)
s = d.get(x)
if s is not None:
s.add(y)
else:
d[x] = set([y])
return d
同事 L 暫時用第二個改法快速地 workaround。不過同事 L 仔細想想覺得或許改用 d.get(x) 會比較好。
但這衍生一個問題:要怎麼和 Python 的 EAFP 原則取得平衡?對於類似的查詢,我們是否都應該要保守地假設 d 有可能是 defaultdict,所以都只能使用 d.get(x) 查詢 dict-like 物件呢?





