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 )
其他回答
在CPython中setdefault的另一个用例是,它在所有情况下都是原子的,而defaultdict将不是原子的,如果你使用从lambda创建的默认值。
cache = {}
def get_user_roles(user_id):
if user_id in cache:
return cache[user_id]['roles']
cache.setdefault(user_id, {'lock': threading.Lock()})
with cache[user_id]['lock']:
roles = query_roles_from_database(user_id)
cache[user_id]['roles'] = roles
如果两个线程执行缓存。同时设置default,它们中只有一个能够创建默认值。
如果你使用defaultdict:
cache = defaultdict(lambda: {'lock': threading.Lock()}
这将导致竞态条件。在我上面的例子中,第一个线程可以创建一个默认锁,第二个线程可以创建另一个默认锁,然后每个线程可以锁定自己的默认锁,而不是每个线程试图锁定单个锁的预期结果。
从概念上讲,setdefault的基本行为是这样的(如果你使用空列表、空dict、int或其他不是用户python代码(如lambda)的默认值,defaultdict也会这样表现):
gil = threading.Lock()
def setdefault(dict, key, value_func):
with gil:
if key not in dict:
return
value = value_func()
dict[key] = value
从概念上讲,defaultdict的基本行为是这样的(只有在使用lambda这样的python代码时-如果使用空列表则不是这样):
gil = threading.Lock()
def __setitem__(dict, key, value_func):
with gil:
if key not in dict:
return
value = value_func()
with gil:
dict[key] = value
我喜欢这里给出的答案:
http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html
简而言之,决策(在非性能关键型应用程序中)应该基于你想如何处理下游空键的查找(即KeyError与默认值)。
我经常使用setdefault,在字典中设置默认值(!!)通常是os。环境字典:
# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')
简单点说,是这样的:
# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
os.environ['VENV_DIR'] = '/my/default/path')
值得注意的是,你也可以使用结果变量:
venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')
但与违约之前相比,这已经不那么必要了。
[编辑]大错特错!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))
我重写了公认的答案,并为新手提供了方便。
#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'],}