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

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


当前回答

我认为上面没有提到的另一个用例。 有时你会根据对象的id来保存一个缓存字典,其中主实例在缓存中,当缺少缓存时你想设置缓存。

return self.objects_by_id.setdefault(obj.id, obj)

当您总是希望每个不同的id保留一个实例时,无论每次如何获取obj,这都很有用。例如,当对象属性在内存中更新并延迟保存到存储中时。

其他回答

当我想要OrderedDict中的默认值时,我使用setdefault()。没有一个标准的Python集合可以同时做到这两点,但是有一些方法可以实现这样的集合。

我通常使用setdefault作为关键字参数字典,例如在这个函数中:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

它非常适合在包装器中围绕接受关键字参数的函数调整参数。

我刚刚偶然发现了一个非常重要的用例: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成员。

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

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

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

setdefault()的不同用例是当您不想覆盖已经设置的键的值时。Defaultdict会覆盖,而setdefault()不会。对于嵌套字典,更常见的情况是,只有在键尚未设置时才设置默认值,因为您不想删除当前子字典。这就是使用setdefault()的时候。

使用defaultdict的示例:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

Setdefault不会覆盖:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}