“setdefault”字典方法的用例
在Python 2.5中增加collections.defaultdict
大大减less了对dict
的setdefault
方法的需求。 这个问题是为了我们的集体教育:
- 现在在Python 2.6 / 2.7中,
setdefault
仍然有用吗? - 什么常见的
setdefault
用例被collections.defaultdict
取代?
你可以说defaultdict
在填充字典之前对设置默认是有用的, setdefault
在填充字典 时或之后设置默认值是有用的 。
可能是最常见的用例:对项目进行分组(在未sorting的数据中,否则使用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 new = defaultdict(list) for (key, value) in data: new[key].append( value ) # all keys have a default already
有时你想确保在创build一个字典之后存在特定的键。 在这种情况下, defaultdict
不起作用,因为它仅在显式访问中创build密钥。 认为你使用的东西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
是很棒的,就像一个新的列表,但是如果它是dynamic的,那么它就不是那么重要。
例如,我需要一个字典来将string映射到唯一的整数。 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
是我构build的一个小类,它自动递增一个int并返回它的值:
class intGen: def __init__(self): self.i = 0 def __call__(self): self.i += 1 return self.i
如果有人有办法做到这一点defaultdict
我很想看到它。
当我想在OrderedDict
使用默认值时,我使用setdefault()
。 没有一个标准的Python集合可以执行这两个操作,但是有一些 方法来实现这样的集合。
正如穆罕默德所说,有些情况下你只是有时想设定一个默认值。 一个很好的例子是数据结构首先被填充,然后被查询。
考虑一个trie。 添加单词时,如果需要一个子节点但不存在,则必须创build该子节点才能扩展树状结构。 当查询单词的存在时,缺less的子节点表示单词不存在,不应该创build。
defaultdict不能这样做。 相反,必须使用get和setdefault方法的常规字典。
从理论上讲,如果你有时想设置一个默认值,有时候还是不行,那么setdefault
仍然会很方便。 在现实生活中,我还没有遇到过这样的用例。
然而,标准库(Python 2.6,_threadinglocal.py)出现了一个有趣的用例:
>>> mydata = local() >>> mydata.__dict__ {'number': 42} >>> mydata.__dict__.setdefault('widgets', []) [] >>> mydata.widgets []
我会说使用__dict__.setdefault
是一个非常有用的例子。
编辑 :碰巧,这是标准库中的唯一例子,它在注释中。 所以可能是不足以certificatesetdefault
存在的理由。 不过,这里是一个解释:
对象将其属性存储在__dict__
属性中。 碰巧, __dict__
属性在对象创build之后的任何时候都是可写的。 这也是一个不是defaultdict
的字典。 对于一般情况下的对象来说,将__dict__
作为defaultdict
是不明智的,因为这会使每个对象都具有作为属性的所有合法标识符。 所以我不能预见任何改变Python对象摆脱__dict__.setdefault
,除了删除它,如果它被认为是没有用的。
这里有一些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'])
defaultdict
( dict.setdefault
)的一个可能的缺点是defaultdict
对象在每个给定的非现有密钥(例如print
, ==
)的情况下创build一个新的项目。 另外defaultdict
类是不那么普遍,然后类dict
(序列化,表示等)。
PS IMO的function(方法)不是要改变一个对象,不应该改变一个对象。
经常使用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')
值得注意的是,你也可以使用结果variables:
venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')
但是这比缺席之前存在的要less得多。
上面提到了我不认为的另一个用例。 有时你保留一个对象的caching字典的主要实例在caching中的ID,你想设置caching丢失。
return self.objects_by_id.setdefault(obj.id, obj)
当你总是希望每个不同的id保持单个实例时,这是非常有用的,不pipe你每次获得obj的方式如何。 例如,当对象属性在内存中更新并且延迟保存到存储器时。
一个非常重要的用例我偶然发现: dict.setdefault()
对于multithreading代码来说非常棒,当你只需要一个规范的对象(而不是多个对象恰好相等)。
例如, Python 3.6.0中的(Int)Flag
枚举有一个错误 :如果多个线程正在竞争复合(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)
解决scheme是使用setdefault()
作为保存计算复合成员的最后一步 – 如果已经保存了另一个复合成员,则使用setdefault()
来代替新成员,从而保证唯一的Enum成员。
[编辑] 非常错误! setdefault总会触发long_computation,Python急于求成。
拓展Tuttle的答案。 对我来说最好的用例是caching机制。 代替:
if x not in memo: memo[x]=long_computation(x) return memo[x]
消耗3行和2或3查找, 我会愉快地写 :
return memo.setdefault(x, long_computation(x))
当所需的默认值不总是相同的,或者只是特定的键需要,但最好不要为其他人提供一个,可以考虑使用setdefault
:
d = {} ... # `i` should default to zero i = d.setdefault(key, 0) ... # `s` should default to an empty string s = d.setdefault(key, '') ...
d = {} ... # v should always default to a list v = d.setdefault(key, []) ... try: # EAFP, but I need the dict to raise a KeyError if the key is not found. w = d[k2] except KeyError: ... ...
我喜欢这里给出的答案:
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}