“setdefault”字典方法的用例

在Python 2.5中增加collections.defaultdict大大减less了对dictsetdefault方法的需求。 这个问题是为了我们的集体教育:

  1. 现在在Python 2.6 / 2.7中, setdefault仍然有用吗?
  2. 什么常见的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']) 

defaultdictdict.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}