Python 2.5 中的新增collections.defaultdict
大大减少了对dict
'setdefault
方法的需求。这个问题是针对我们的集体教育的:
setdefault
今天在 Python 2.6/2.7 中还有什么用处?- 哪些流行的用例
setdefault
被取代了collections.defaultdict
?
Python 2.5 中的新增collections.defaultdict
大大减少了对dict
'setdefault
方法的需求。这个问题是针对我们的集体教育的:
setdefault
今天在 Python 2.6/2.7 中还有什么用处?setdefault
被取代了collections.defaultdict
?您可以说在填充 dict 之前defaultdict
设置默认值很有用,并且对于在填充 dict 时或之后设置默认值很有用。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
有时您想确保在创建 dict 后存在特定的键。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 )
我通常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)
它非常适合在使用关键字参数的函数的包装器中调整参数。
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())
请注意,这dict.get(key, nextID())
是不够的,因为我以后还需要能够引用这些值。
intGen
是我构建的一个小类,它自动递增一个 int 并返回它的值:
class intGen:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
如果有人有办法做到这一点,defaultdict
我很乐意看到它。
正如大多数答案所说setdefault
,或者defaultdict
当键不存在时,您可以设置默认值。但是,我想指出一个关于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
如您所见,print
即使字典中已经存在 2 也已执行。如果您计划使用setdefault
例如memoization
. 如果将递归函数调用添加为 的第二个参数setdefault
,则不会从中获得任何性能,因为 Python 将始终递归调用该函数。
由于提到了 memoization,如果您考虑使用 memoization 增强功能,更好的选择是使用 functools.lru_cache 装饰器。lru_cache 更好地处理递归函数的缓存要求。
正如穆罕默德所说,在某些情况下,您有时只希望设置默认值。一个很好的例子是一个数据结构,它首先被填充,然后被查询。
考虑试一试。添加单词时,如果需要子节点但不存在,则必须创建它以扩展 trie。当查询一个词是否存在时,一个缺失的子节点表示该词不存在并且不应该被创建。
defaultdict 无法做到这一点。相反,必须使用带有 get 和 setdefault 方法的常规字典。
从理论上讲,setdefault
如果您有时想设置默认值而有时不想设置,仍然会很方便。在现实生活中,我还没有遇到过这样的用例。
然而,一个有趣的用例来自标准库(Python 2.6,_threadinglocal.py):
>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]
我会说 using__dict__.setdefault
是一个非常有用的案例。
编辑:碰巧,这是标准库中唯一的示例,它在评论中。因此,可能不足以证明存在的理由setdefault
。不过,这里有一个解释:
对象将其属性存储在__dict__
属性中。碰巧的是,该__dict__
属性在对象创建后的任何时间都是可写的。它也是字典而不是defaultdict
. __dict__
在一般情况下,将对象作为 a是不明智的,defaultdict
因为这会使每个对象都具有所有合法标识符作为属性。因此,我无法预见 Python 对象会发生任何更改,__dict__.setdefault
除非它被认为没有用,否则会完全删除它。
defaultdict
over dict
( dict.setdefault
) 的一个缺点是,每次给定不存在的键(例如,使用, )时,defaultdict
对象都会创建一个新项目。此外,该类通常不如该类常见,它更难以序列化 IME。==
print
defaultdict
dict
PS 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'],}
下面是一些 setdefault 的例子来展示它的用处:
"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)
# To retrieve a list of the values for a key
list_of_values = d[key]
# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)
# Despite the empty lists, it's still possible to
# test for the existance of values easily:
if d.has_key(key) and d[key]:
pass # d has some values for key
# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e
# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])
我经常使用 setdefault,得到这个,在字典中设置一个默认值(!!!);os.environ 字典有点常见:
# 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')
但这比 defaultdicts 存在之前的必要性要小。
我认为上面没有提到的另一个用例。有时,您通过对象的 id 保留对象的缓存字典,其中主实例位于缓存中,并且您希望在丢失时设置缓存。
return self.objects_by_id.setdefault(obj.id, obj)
当您总是想为每个不同的 id 保留一个实例时,这很有用,无论您每次如何获得一个 obj 。例如,当对象属性在内存中更新并延迟保存到存储时。
我刚刚偶然发现的一个非常重要的用例: dict.setdefault()
当您只需要一个规范对象(而不是碰巧相等的多个对象)时,它非常适合多线程代码。
例如,(Int)Flag
Python 3.6.0 中的 Enum 有一个错误:如果多个线程竞争一个复合(Int)Flag
成员,最终可能会有多个:
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 成员。
[编辑]大错特错!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))
我喜欢这里给出的答案:
http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html
简而言之,应该根据您希望如何处理下游空键的查找(即 KeyError
与默认值)来做出决定(在非性能关键型应用程序中)。
不同的用例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}
除了建议的内容之外,setdefault
在您不想修改已设置的值的情况下可能很有用。例如,当您有重复的数字并且您想将它们视为一组时。在这种情况下,如果您遇到duplicate
已经设置的重复键,您将不会更新该键的值。您将保留第一个遇到的值。就好像您只迭代/更新一次重复的键一样。
这是记录排序列表的键/元素的索引的代码示例:
nums = [2,2,2,2,2]
d = {}
for idx, num in enumerate(sorted(nums)):
# This will be updated with the value/index of the of the last repeated key
# d[num] = idx # Result (sorted_indices): [4, 4, 4, 4, 4]
# In the case of setdefault, all encountered repeated keys won't update the key.
# However, only the first encountered key's index will be set
d.setdefault(num,idx) # Result (sorted_indices): [0, 0, 0, 0, 0]
sorted_indices = [d[i] for i in nums]
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
如果两个线程cache.setdefault
同时执行,则只有其中一个线程能够创建默认值。
相反,如果您使用默认字典:
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