'setdefault'dict方法的用例


Answers:


208

您可以说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 )

1
实际上,此IMHO是替换为的主要用例defaultdict。您能否在第一段中举例说明您的意思?
伊莱·班德斯基

2
穆罕默德·阿尔卡鲁里(Muhammad Alkarouri):首先要做的是复制字典,然后覆盖某些项目。我也经常这样做,我想这实际上是最喜欢的习惯用法setdefault。一defaultdict,另一方面是行不通的,如果不是所有的defaultvalues都相等(即有些是0,有些是[])。
Jochen Ritzel

2
@ YHC4k,是的。那就是为什么我用过headers = dict(optional_headers)。对于默认值不完全相等的情况。最终结果与先获取HTTP标头然后为未获取的HTTP标头设置默认值相同。如果您已经拥有了,那将非常有用optional_headers。试试我给定的两步代码,并将其与您的代码进行比较,您会明白我的意思。
Muhammad Alkarouri,2010年

19
或只是做new.setdefault(key, []).append(value)
fmalina

2
我觉得最好的答案归结defaultdict为甚至要比setdefault(这好在现在的用例在哪里)更好。同样,ChainMap将更好地处理该http示例,即IMO。
YvesgereY

29

我常用 setdefault关键字自变量dict,例如在此函数中:

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)

这对于在带有关键字参数的函数周围的包装器中调整参数非常有用。


16

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我很乐意看到它。


换一种方式与(的子类)defaultdict做到这一点,看到了这个问题:stackoverflow.com/questions/2912231/...
薇罗尼卡

8
您可以替换intGenitertools.count().next
锑2012年

7
nextID()的值每次myDict.setdefault()调用都会增加,即使返回的值未用作strID。这似乎在某种程度上是浪费的,并且说明了我通常不喜欢的一件事setdefault()-即它总是评估其default参数是否真正被使用。
martineau 2013年

你可以这样做defaultdictmyDict = defaultdict(lambda: nextID())。后来,strID = myDict[myStr]在循环中。
musiphil

3
为了获得您使用defaultdict描述的行为,为什么不这样做myDict = defaultdict(nextID)呢?
47

10

setdefault()要在中使用默认值时使用OrderedDict。没有一个标准的Python集合可以做到这两种,但是有一些 方法可以实现这样的集合。


9

正如大多数答案所说的那样,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始终会递归地调用该函数。

由于提到了备忘录,如果您考虑使用备忘录增强功能,则更好的替代方法是使用functools.lru_cache装饰器。lru_cache可以更好地处理递归函数的缓存要求。


8

正如穆罕默德所说,在某些情况下,您有时只希望设置默认值。一个很好的例子是首先填充然后查询的数据结构。

考虑一个特里。添加单词时,如果需要但不存在子节点,则必须创建该子节点以扩展该Trie。查询单词是否存在时,缺少子节点表示该单词不存在,因此不应创建。

defaultdict无法做到这一点。相反,必须使用带有get和setdefault方法的常规dict。


5

从理论上讲,setdefault如果您有时想设置默认值而有时又不想设置默认值,那。在现实生活中,我还没有遇到过这样的用例。

但是,标准库(Python 2.6,_threadinglocal.py)提出了一个有趣的用例:

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

我会说使用 __dict__.setdefault是一个非常有用的案例。

编辑:碰巧的是,这是标准库中的唯一示例,并且在注释中。因此,可能不足以证明存在setdefault。不过,这里有一个解释:

对象将其属性存储在__dict__属性中。碰巧的是,该__dict__属性在对象创建后随时可以写入。这也是一本字典,而不是一本字典defaultdict。在一般情况下,将对象__dict__作为一个对象是不明智的,defaultdict因为这会使每个对象都具有所有合法标识符作为属性。因此,我无法预见对Python对象的任何更改都将被__dict__.setdefault删除,除非它被认为没有用,否则将其全部删除。


1
您能否详细说明-是什么使_dict .setdefault特别有用?
伊莱·班德斯基

1
@Eli:我认为关键__dict__是通过实现a dict而不是a defaultdict
卡特里尔

1
好的。我不介意setdefault留在Python中,但是很奇怪地看到它现在几乎没有用了。
伊莱·本德斯基

@Eli:我同意。我不认为没有足够的理由在今天引入它。但是已经存在了,鉴于已经使用了所有代码,因此很难争辩删除它。
Muhammad Alkarouri,2010年

1
防御性编程下的文件。setdefault明确表明您是通过可能存在或可能不存在的键来分配字典,如果不存在,则希望使用默认值创建它:例如d.setdefault(key,[]).append(value)。在程序的其他地方alist=d[k],您要在其中计算k,并且如果k in不在d中,则希望引发异常(使用defaultdict可能assert k in d甚至if not ( k in d): raise KeyError
需要这样做

3

的一个缺点defaultdict超过dictdict.setdefault)是一个defaultdict对象来创建一个新的项目,每次不存在的关键是给出(例如,使用==print)。同样,defaultdict该类通常不如dict该类常见,因此很难将其序列化为IME。

PS IMO功能并非意在使对象发生变异,而不应使对象发生变异。


不必每次都创建一个新对象。您可以轻松地defaultdict(lambda l=[]: l)代替。
Artyer

6
永远不要执行@Artyer的建议-可变默认值会咬你。
布兰登·汉珀

2

以下是一些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'])

2

我改写了接受的答案,并为新手提供了便利。

#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'],}

1

当在字典中设置默认值(!!!)时,我经常使用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之前的必要性要小。


1

上面没有提到另一个我不认为的用例。有时,您通过对象的ID保留对象的缓存字典,其中主要实例位于缓存中,而您想在丢失对象时设置缓存。

return self.objects_by_id.setdefault(obj.id, obj)

当您始终希望每个唯一的ID保留一个实例时,无论您每次如何获取obj,这都非常有用。例如,当对象属性在内存中更新并推迟保存到存储时。


1

我偶然发现了一个非常重要的用例: dict.setdefault()当您只需要一个规范的对象(而不是恰好相等的多个对象)时,它非常适合多线程代码。

例如,(Int)FlagPython 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成员。


0

[编辑] 非常错误!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))

好的例子。我仍然认为这3条线更容易理解,但也许我的大脑会逐渐喜欢setdefault。
鲍勃·斯坦因

5
这些不相等。在第一个中,long_computation(x)仅在时调用x not in memo。而在第二个中,long_computation(x)总是被调用。只有赋值是有条件的,等效的代码setdefault如下所示:v = long_computation(x)/ if x not in memo:/ memo[x] = v
Dan D.


0

不同的用例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}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.