38

一个小烦恼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())

这会产生如下输出:

noisy_default: returning 4063267
4063267
noisy_default: returning 628989
4063267

正如最后一行所证实的,第二次执行noisy_default是不必要的,因为此时键1已经存在于d(带有 value 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))

现在,片段

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

产生这样的输出:

noisy_default: returning 5025427
5025427
5025427

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

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

如果一个人想要传递一个可调用对象作为默认值本身(即,意味着被调用),或者如果要调用的可调用对象需要参数,lambda:请在适当的参数之前添加。例如:

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

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

那些不喜欢重写getand的想法setdefault和/或由此产生的需要测试可调用性等的人可以改用这个版本:

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

4 回答 4

24

这也可以通过 来完成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

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

于 2014-10-23T08:07:17.830 回答
13

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

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

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

value = cache.get(key) or cache.setdefault(key, func(key))
于 2016-02-05T10:16:42.403 回答
11

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

于 2013-07-08T17:53:19.017 回答
-1

似乎没有不需要额外的类或额外的查找的单线。作为记录,这里有一个简单(甚至不简洁)的方法来实现这一点,而无需它们中的任何一个。

try:
    value = dct[key]
except KeyError:
    value = noisy_default()
    dct[key] = value
return value
于 2018-06-30T03:03:57.220 回答