如何实现一个懒惰的setdefault?

kjo*_*kjo 24 python lazy-evaluation

一个小麻烦dict.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())
Run Code Online (Sandbox Code Playgroud)

这产生如下的ouptut:

noisy_default: returning 4063267
4063267
noisy_default: returning 628989
4063267
Run Code Online (Sandbox Code Playgroud)

当最后一行确认时,第二次执行noisy_default是不必要的,因为此时密钥1已经存在于d(带有值4063267)中.

是否有可能实现一个子类,dictsetdefault方法懒惰地计算其第二个参数?


编辑:

以下是受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))
Run Code Online (Sandbox Code Playgroud)

现在,片段

d = LazyDict()
print d.setdefault(1, noisy_default)
print d.setdefault(1, noisy_default)
Run Code Online (Sandbox Code Playgroud)

产生这样的输出:

noisy_default: returning 5025427
5025427
5025427
Run Code Online (Sandbox Code Playgroud)

请注意,d.setdefault上面的第二个参数现在是可调用的,而不是函数调用.

当第二个参数为LazyDict.getLazyDict.setdefault不是可调用时,它们的行为方式与相应的dict方法相同.

如果想要将一个callable作为默认值本身传递(即,打算被调用),或者如果要调用的callable需要参数,则在前面lambda:添加适当的参数.例如:

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

d2.setdefault('foo', lambda: bar('frobozz'))
Run Code Online (Sandbox Code Playgroud)

这些谁不喜欢压倒一切的思想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()))
Run Code Online (Sandbox Code Playgroud)

San*_*nta 18

这也可以用defaultdict.它用一个callable实例化,然后在访问一个不存在的元素时调用它.

from collections import defaultdict

d = defaultdict(noisy_default)
d[1] # noise
d[1] # no noise
Run Code Online (Sandbox Code Playgroud)

需要注意的defaultdict是,callable没有参数,因此您无法从密钥中获取默认值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
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅集合模块.


Pav*_*sov 10

不,在调用之前发生了对参数的评估.您可以实现一个setdefault类似于函数的函数,该函数将可调用作为其第二个参数,并仅在需要时调用它.


Ces*_*ssa 7

您可以使用三元运算符在单行中执行此操作:

value = cache[key] if key in cache else cache.setdefault(key, func(key))
Run Code Online (Sandbox Code Playgroud)

如果您确定cache永远不会存储虚假值,您可以稍微简化一下:

value = cache.get(key) or cache.setdefault(key, func(key))
Run Code Online (Sandbox Code Playgroud)

  • 如果您正在检查`dict in dict',那么使用`setdeault`是没有意义的 (4认同)
  • 这将需要在“ cache”中搜索“ key”两次。对于基于Hash-Map的字典来说,这并不是什么大问题,但仍然没有什么意义。 (2认同)
  • @user1685095 如果您不调用 setdefault 缓存将不会更新。setdefault 既设置空缓存又同时返回其值 (2认同)