2013-07-08 93 views
13

dict.setdefault的一個小問題是它總是評估它的第二個參數(當然給出的時候),即使第一個參數已經是字典中的一個鍵。如何實現一個懶惰的setdefault?

例如:

import random 
def noisy_default(): 
    ret = random.randint(0, 10000000) 
    print 'noisy_default: returning %d' % ret 
    return ret 

d = dict() 
print d.setdefault(1, noisy_default()) 
print d.setdefault(1, noisy_default()) 

這產生ouptut類似如下:

noisy_default: returning 4063267 
4063267 
noisy_default: returning 628989 
4063267 

作爲最後行可確認,的noisy_default第二執行是不必要的,因爲通過這一點的關鍵1是已經存在於d(值爲4063267)。

是否有可能實現dict的子類,其setdefault方法懶惰地評估其第二個參數?


編輯:

下面是BrenBarn的評論和Pavel Anossov的回答靈感的實現。儘管如此,我還是繼續實現了get的懶惰版本,因爲其基本思想本質上是相同的。

class LazyDict(dict): 
    def get(self, key, thunk=None): 
     return (self[key] if key in self else 
       thunk() if callable(thunk) else 
       thunk) 


    def setdefault(self, key, thunk=None): 
     return (self[key] if key in self else 
       dict.setdefault(self, key, 
           thunk() if callable(thunk) else 
           thunk)) 

現在,片斷

d = LazyDict() 
print d.setdefault(1, noisy_default) 
print d.setdefault(1, noisy_default) 

產生輸出這樣的:

noisy_default: returning 5025427 
5025427 
5025427 

注意,上述的第二個參數是d.setdefault現在一個可調用的,而不是函數調用。

LazyDict.getLazyDict.setdefault的第二個參數不可調用時,它們的行爲方式與對應的dict方法相同。

如果一個人想通過一個可調用作爲默認值本身(即,不意味着被調用),或者如果可調用被調用需要參數,前置lambda:到相應的參數。例如:

d1.setdefault('div', lambda: div_callback) 

d2.setdefault('foo', lambda: bar('frobozz')) 

那些誰不喜歡重寫getsetdefault的想法,和/或導致需要測試可召集等,都可以使用這個版本來代替:

class LazyButHonestDict(dict): 
    def lazyget(self, key, thunk=lambda: None): 
     return self[key] if key in self else thunk() 


    def lazysetdefault(self, key, thunk=lambda: None): 
     return (self[key] if key in self else 
       self.setdefault(key, thunk())) 
+0

你不能讓它不評估第二個參數。你必須做的是將該參數封裝在一個函數中(例如,用'lambda'),然後讓'setdefault'只在需要時調用該函數。 – BrenBarn

+0

我可以建議你將'* args,** kwargs'添加到'lazyget',lazysetdefault'和對'thunk()'的調用中嗎?這將允許你的懶惰的東西參數。例如'lbd.lazysetdefault('total',sum,[1,2,3,4],start = 2)' – Hounshell

回答

6

不,在調用之前發生爭論評估。您可以實現類似setdefault的函數,該函數將可調用函數作爲其第二個參數,並僅在需要時調用它。

9

這也可以用defaultdict來完成。它被一個可調用的實例化,然後當一個不存在的元素被訪問時被調用。

from collections import defaultdict 

d = defaultdict(noisy_default) 
d[1] # noise 
d[1] # no noise 

defaultdict需要說明的是,調用變得沒有參數,這樣你可以用dict.setdefault你不能從密鑰派生的默認值。這可以通過在子類中重寫__missing__得到緩解:

from collections import defaultdict 

class defaultdict2(defaultdict): 
    def __missing__(self, key): 
     value = self.default_factory(key) 
     self[key] = value 
     return value 

def noisy_default_with_key(key): 
    print key 
    return key + 1 

d = defaultdict2(noisy_default_with_key) 
d[1] # prints 1, sets 2, returns 2 
d[1] # does not print anything, does not set anything, returns 2 

欲瞭解更多信息,請參閱collections模塊。

4

您可以使用三元運算符做,在一個班輪:

value = cache[key] if key in cache else cache.setdefault(key, func(key)) 

如果您確信該cache絕不會存儲falsy值,你可以把它簡化一點:

value = cache.get(key) or cache.setdefault(key, func(key)) 
+1

如果你正在檢查'鍵入字典'有沒有用'setdeault' – user1685095

+1

這將需要在'cache'中搜索'key'兩次。對於基於哈希映射的字典而言,這並不是什麼大不了的事情,但仍然沒有那麼聰明。 –

+0

@ user1685095如果您不調用setdefault,則不會更新緩存。 setdefault既設置空的緩存並同時返回其值 –