Python 2.5中增加的collections.defaultdict极大地减少了对dict的setdefault方法的需求。这个问题是为了我们的集体教育:
在今天的Python 2.6/2.7中,setdefault在哪些方面仍然有用? setdefault的哪些流行用例被collections.defaultdict所取代?
Python 2.5中增加的collections.defaultdict极大地减少了对dict的setdefault方法的需求。这个问题是为了我们的集体教育:
在今天的Python 2.6/2.7中,setdefault在哪些方面仍然有用? setdefault的哪些流行用例被collections.defaultdict所取代?
当前回答
你可以说defaultdict用于在填充dict之前设置默认值,而setdefault用于在填充dict期间或之后设置默认值。
可能是最常见的用例:对项进行分组(在未排序的数据中,否则使用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-ish——有些是可选的,但你想要它们的默认值:
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 )
其他回答
我刚刚偶然发现了一个非常重要的用例: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在默认值是静态时很好,就像一个新列表,但如果它是动态的,就不那么好了。
例如,我需要一个字典来映射字符串到唯一的整数。Defaultdict (int)将始终使用0作为默认值。同样,defaultdict(intGen())总是生成1。
相反,我用了一个普通的词典:
nextID = intGen()
myDict = {}
for lots of complicated stuff:
#stuff that generates unpredictable, possibly already seen str
strID = myDict.setdefault(myStr, nextID())
注意这个词典。get(key, nextID())是不够的,因为我需要能够在以后引用这些值。
intGen是我构建的一个小类,它自动递增int并返回它的值:
class intGen:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
如果有人有办法做到这一点与defaultdict,我很乐意看到它。
defaultdict相对于dict (dict.setdefault)的一个缺点是,defaultdict对象每次给出不存在的键时都会创建一个新项(例如==,print)。此外,defaultdict类通常比dict类更不常见,它更难IME序列化。
注:IMO函数|方法不意味着改变对象,不应该改变对象。
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}
正如大多数答案,state setdefault或defaultdict将允许您在键不存在时设置默认值。然而,我想指出一个关于setdefault用例的小警告。当Python解释器执行时,setdefaultit将始终计算函数的第二个参数,即使该键存在于字典中。例如:
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来进行诸如内存之类的优化,这就变得尤为重要。如果将递归函数调用作为setdefault的第二个参数,则不会从中获得任何性能,因为Python总是递归地调用该函数。
既然提到了内存,一个更好的选择是使用functools。Lru_cache装饰器,如果考虑使用内存增强函数。Lru_cache可以更好地处理递归函数的缓存需求。