dict' 的 'setdefault' 方法的用例

245 投票
18 回答
140551 浏览
提问于 2025-04-16 02:47

在Python 2.5中加入了collections.defaultdict,这大大减少了我们使用dictsetdefault方法的需求。这个问题是为了让大家一起学习:

  1. 在Python 2.6/2.7中,setdefault现在还有什么用处呢?
  2. 有哪些常见的setdefault用法被collections.defaultdict取代了呢?

18 个回答

23

大多数回答提到,setdefaultdefaultdict 可以让你在字典中找不到某个键时设置一个默认值。不过,我想提醒大家一个小问题,就是关于 setdefault 的使用场景。当 Python 运行 setdefault 时,它会始终计算这个函数的第二个参数,即使这个键在字典里已经存在。例如:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

你可以看到,即使数字 2 已经在字典里了,print 还是被执行了。如果你打算把 setdefault 用于一些优化,比如说 memoization(记忆化),这个问题就特别重要了。如果你把一个递归函数调用作为 setdefault 的第二个参数,你就得不到任何性能上的提升,因为 Python 会一直递归调用这个函数。

既然提到了记忆化,如果你想给一个函数加上记忆化,使用 functools.lru_cache 装饰器会是一个更好的选择。lru_cache 更好地处理递归函数的缓存需求。

37

我经常使用 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)

这个方法非常适合在处理那些需要关键字参数的函数时,调整参数的值。

266

你可以理解为,defaultdict 是在填充字典之前设置默认值的工具,而 setdefault 则是在填充字典时或之后设置默认值的工具。

最常见的用法可能是:对项目进行分组(在数据未排序的情况下,如果数据已经排序,可以使用 itertools.groupby)。

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


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


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

有时候,你想确保在创建字典后某些特定的键是存在的。这个时候 defaultdict 就不太适用了,因为它只会在你明确访问某个键时才创建这个键。想象一下,你在处理一些类似 HTTP 的东西,有很多头部信息——其中一些是可选的,但你希望为它们设置默认值:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

撰写回答