Python 2.5中增加的collections.defaultdict极大地减少了对dict的setdefault方法的需求。这个问题是为了我们的集体教育:

在今天的Python 2.6/2.7中,setdefault在哪些方面仍然有用? setdefault的哪些流行用例被collections.defaultdict所取代?


当前回答

[编辑]大错特错!setdefault总是会触发long_computation,因为Python很急切。

扩展塔特尔的答案。对我来说,最好的用例是缓存机制。而不是:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

这需要3行和2到3次查找,我很乐意这样写:

return memo.setdefault(x, long_computation(x))

其他回答

正如Muhammad所说,在某些情况下,您只是偶尔希望设置默认值。一个很好的例子是数据结构,首先填充,然后查询。

考虑一个例子。在添加单词时,如果需要子节点但不存在,则必须创建子节点以扩展树。在查询单词是否存在时,缺少子节点表示该单词不存在,不应该创建它。

defaultdict不能这样做。相反,必须使用带有get和setdefault方法的常规dict。

从理论上讲,如果您有时想设置默认值,有时不想设置默认值,那么setdefault仍然很方便。在现实生活中,我还没有遇到过这样的用例。

然而,一个有趣的用例来自标准库(Python 2.6, _threadinglocal.py):

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

我会说使用__dict__。Setdefault是一个非常有用的例子。

编辑:碰巧,这是标准库中唯一的示例,并且它在注释中。因此,它可能不足以证明setdefault的存在。不过,这里有一个解释:

Objects store their attributes in the __dict__ attribute. As it happens, the __dict__ attribute is writeable at any time after the object creation. It is also a dictionary not a defaultdict. It is not sensible for objects in the general case to have __dict__ as a defaultdict because that would make each object having all legal identifiers as attributes. So I can't foresee any change to Python objects getting rid of __dict__.setdefault, apart from deleting it altogether if it was deemed not useful.

我刚刚偶然发现了一个非常重要的用例:dict.setdefault()对于只想要单个规范对象(而不是恰好相等的多个对象)的多线程代码非常有用。

例如,Python 3.6.0中的(Int)标志Enum有一个错误:如果多个线程在竞争一个复合(Int)标志成员,最终可能会有多个:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

解决方案是使用setdefault()作为保存计算的组合成员的最后一步——如果已经保存了另一个成员,则使用它而不是新成员,从而保证唯一的Enum成员。

defaultdict相对于dict (dict.setdefault)的一个缺点是,defaultdict对象每次给出不存在的键时都会创建一个新项(例如==,print)。此外,defaultdict类通常比dict类更不常见,它更难IME序列化。

注:IMO函数|方法不意味着改变对象,不应该改变对象。

我重写了公认的答案,并为新手提供了方便。

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

另外,我将这些方法分类为参考:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}