2013-07-08 21 views
13

dict.setdefaultの小さな悩みは、最初の引数が既に辞書のキーであっても、常に2番目の引数(もちろん与えられたとき)を評価するということです。例えばlazy setdefaultを実装する方法は?

:これは、以下のようなouptut生成

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()) 

:最後の行が確認よう

noisy_default: returning 4063267 
4063267 
noisy_default: returning 628989 
4063267 

をキー1である。この点によってので、noisy_defaultの第二の実行は、不要ですd(値4063267)に既に存在します。

setdefaultメソッドが第2引数を遅延評価するdictのサブクラスを実装することは可能ですか?


EDIT:

以下

がBrenBarnさんのコメントとパベルAnossovの答えに触発実装です。その間、基本的なアイデアは本質的に同じなので、私は先に進み、怠惰なバージョンを実装しました。上記d.setdefaultへの第2引数は今呼び出し可能ではなく、関数呼び出しであることを

noisy_default: returning 5025427 
5025427 
5025427 

は予告:

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) 

は、このような出力を生成します。

LazyDict.getまたはLazyDict.setdefaultの第2引数が呼び出し可能でない場合、それらは対応するdictメソッドと同じように動作します。 1はデフォルト値そのものとして呼び出し可能に合格したい場合

(すなわち、はないと呼ばれることを意図し)、またはと呼ばれることが呼び出し可能な場合には、引数を必要とし、先頭に追加適切な引数に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

第2引数を評価しないようにすることはできません。あなたがしなければならないことは、その引数を関数(例えば、 'lambda ')で囲み、必要に応じて' setdefault'関数を呼び出すことだけです。 – BrenBarn

+0

'lazyget'、' lazysetdefault'の署名と 'thunk()'の呼び出しに '* args、** kwargs'を追加することを提案できますか?これはあなたの怠惰なものがパラメータを取ることができます。例えば'ldap.lazysetdefault( 'total'、sum、[1,2,3,4]、start = 2)' – Hounshell

答えて

6

いいえ、議論の評価はコールの前に行われます。 setdefaultのような関数を実装することができます。この関数は、第2引数として呼び出し可能関数をとり、必要な場合にのみ呼び出します。

9

これはdefaultdictでも実行できます。存在しない要素がアクセスされたときに呼び出される呼び出し可能関数でインスタンス化されます。

from collections import defaultdict 

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

defaultdictと注意点は、呼び出し可能には、引数を取得していないということですので、あなたがdict.setdefaultでできたとして、あなたは、キーから既定値を導出することはできません。詳細については

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モジュールを参照してください。これは、サブクラスで__missing__をオーバーライドすることによって緩和することができます。あなたはcacheはfalsy値を格納しないことを確信している場合、あなたはそれを少し簡略化することができ

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

4

あなたはワンライナーで三項演算子を使用していることを行うことができます

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

'' key in dict'をチェックしているなら、 'setdeault'を使う意味がありません。 – user1685095

+1

これは、 'cache'で' key'を2回探します。これはハッシュマップに基づく辞書の大したことではありませんが、それでもマッチ感はありません。 –

+0

@ user1685095 setdefaultを呼び出さないと、キャッシュは更新されません。 setdefaultは空のキャッシュを設定し、同時にその値を返す –

関連する問題