python dict:get vs setdefault

Cer*_*rno 46 python dictionary get setdefault

以下两个表达式似乎与我相同.哪个更好?

data = [('a', 1), ('b', 1), ('b', 2)]

d1 = {}
d2 = {}

for key, val in data:
    # variant 1)
    d1[key] = d1.get(key, []) + [val]
    # variant 2)
    d2.setdefault(key, []).append(val)
Run Code Online (Sandbox Code Playgroud)

结果是一样的,但哪个版本更好或更富有pythonic?

就个人而言,我觉得版本2难以理解,因为对我而言,setdefault非常难以掌握.如果我理解正确,它会在字典中查找"key"的值,如果不可用,则在"dict"中输入"[]",返回对值或"[]"的引用,并在其中附加"val"参考.虽然顺利但它至少不是直观的(至少对我而言).

在我看来,版本1更容易理解(如果可用,获取"key"的值,如果没有,获取"[]",然后加入由[val]组成的列表并将结果放在"key"中).但是,虽然更直观地理解,但我担心这个版本的性能会降低,所有这些列表都会创建.另一个缺点是"d1"在表达式中出现两次,这是相当容易出错的.可能有一个更好的实现使用get,但目前它没有我.

我的猜测是版本2虽然对于没有经验的人来说更难掌握,但速度更快,因此更可取.意见?

agf*_*agf 25

你的两个例子做了同样的事情,但这并不意味着getsetdefault做.

两者之间的差异基本上是每次手动设置d[key]指向列表,而不是仅在未设置时setdefault自动设置d[key]到列表.

让这两种方法尽可能相似,我跑了

from timeit import timeit

print timeit("c = d.get(0, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("c = d.get(1, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(0, []).extend([1])", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(1, []).extend([1])", "d = {1: []}", number = 1000000)
Run Code Online (Sandbox Code Playgroud)

得到了

0.794723378711
0.811882272256
0.724429205999
0.722129751973
Run Code Online (Sandbox Code Playgroud)

所以setdefaultget此目的快10%左右.

get方法允许您尽可能少地使用setdefault.KeyError即使您不想设置密钥,也可以使用它来避免在密钥不存在时(如果这种情况经常发生).

请参阅"setdefault"dict方法的用例,dict.get()方法返回指针,以获取有关这两种方法的更多信息.

关于setdefault大多数时候,你想要使用的结论defaultdict.线程get得出的结论是它很慢,并且通常你最好(速度明智)做双重查找,使用defaultdict或处理错误(取决于字典的大小和你的用例).

  • 请注意,您的 `setdefault` 版本实际上从未将构造的列表存储回字典中,因此您将通过添加构造 10,000 个元素列表的代码与构造 10,000 个 1 元素列表并将它们丢弃的代码进行比较。有关更多信息,请参阅我的答案。 (3认同)

Dun*_*can 16

agf接受的答案并不是与之相比.后:

print timeit("d[0] = d.get(0, []) + [1]", "d = {1: []}", number = 10000)
Run Code Online (Sandbox Code Playgroud)

d[0] 包含一个包含10,000个项目的列表,而在:

print timeit("d.setdefault(0, []) + [1]", "d = {1: []}", number = 10000)
Run Code Online (Sandbox Code Playgroud)

d[0]很简单[].即d.setdefault版本永远不会修改存储的列表d.代码应该是:

print timeit("d.setdefault(0, []).append(1)", "d = {1: []}", number = 10000)
Run Code Online (Sandbox Code Playgroud)

事实上比错误的setdefault例子更快.

这里的区别实际上是因为当你使用串联追加时,每次都会复制整个列表(并且一旦你有10,000个元素开始变得可测量.使用append列表更新是分摊O(1),即有效的恒定时间.

最后,在原始问题中还没有考虑其他两个选项:defaultdict或者只是测试字典以查看它是否已包含密钥.

所以,假设 d3, d4 = defaultdict(list), {}

# variant 1 (0.39)
d1[key] = d1.get(key, []) + [val]
# variant 2 (0.003)
d2.setdefault(key, []).append(val)
# variant 3 (0.0017)
d3[key].append(val)
# variant 4 (0.002)
if key in d4:
    d4[key].append(val)
else:
    d4[key] = [val]
Run Code Online (Sandbox Code Playgroud)

变体1是迄今为止最慢的,因为它每次复制列表,变量2是第二慢,变体3是最快的但是如果你需要早于2.5的Python则不会工作,而变体4只比变体3略慢.

如果可以的话,我会说使用变体3,变量4作为选项,适用于那些defaultdict不太合适的偶然地方.避免使用两种原始变体.

  • 你是对的,我的例子是错误的,但幸运的是它们没有指向错误的方向。我已经修复了一些。 (3认同)

pyA*_*ict 11

对于那些仍在努力理解这两个术语的人,让我告诉您 get() 和 setdefault() 方法之间的基本区别 -

场景一

root = {}
root.setdefault('A', [])
print(root)
Run Code Online (Sandbox Code Playgroud)

场景 2

root = {}
root.get('A', [])
print(root)
Run Code Online (Sandbox Code Playgroud)

在场景 1 中的输出将{'A': []}在场景 2 中{}

因此setdefault(),在字典中设置不存在的键,同时get()只为您提供默认值,但不会修改字典。

现在让我们来看看这将是有用的 - 假设您正在字典中搜索其值为列表的元素,并且您想要修改该列表,如果找到,则使用该列表创建一个新键。

使用 setdefault()

def fn1(dic, key, lst):
    dic.setdefault(key, []).extend(lst)
Run Code Online (Sandbox Code Playgroud)

使用 get()

def fn2(dic, key, lst):
    dic[key] = dic.get(key, []) + (lst) #Explicit assigning happening here
Run Code Online (Sandbox Code Playgroud)

现在让我们检查时间 -

dic = {}
%%timeit -n 10000 -r 4
fn1(dic, 'A', [1,2,3])
Run Code Online (Sandbox Code Playgroud)

耗时 288 纳秒

dic = {}
%%timeit -n 10000 -r 4
fn2(dic, 'A', [1,2,3])
Run Code Online (Sandbox Code Playgroud)

花了 128 秒

因此,这两种方法之间存在非常大的时序差异。


gri*_*ton 10

您可能希望defaultdictcollections模块中查看.以下等同于您的示例.

from collections import defaultdict

data = [('a', 1), ('b', 1), ('b', 2)]

d = defaultdict(list)

for k, v in data:
    d[k].append(v)
Run Code Online (Sandbox Code Playgroud)

还有更多的在这里.

  • "我尽力避免额外的进口".请停止这样做.这是一个非常糟糕的政策,导致不必要的复杂程序. (6认同)
  • 我知道这一点,但我想知道哪个原始版本更可取.或者你会说使用defaultdict始终是要走的路吗?我尽量避免额外的进口,因此我通常会使用传统的词典.但这可能是愚蠢的? (2认同)

use*_*ser 5

1.在此处用一个很好的例子进行解释:http :
//code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/

字典 setdefault典型用法
somedict.setdefault(somekey,[]).append(somevalue)

字典 获得典型用法
theIndex[word] = 1 + theIndex.get(word,0)


2.更多说明:http : //python.net/~goodger/projects/pycon/2007/idiomatic/handout.html

dict.setdefault()等于getset & get。或者set if necessary then get。如果您的字典键计算起来很昂贵或键入时间很长,则特别有效。

dict.setdefault()的唯一问题是,是否需要始终评估默认值。这只是重要的如果默认值是计算昂贵。在这种情况下,请使用defaultdict。


3.最后,不同之处的官方文档突出显示了 http://docs.python.org/2/library/stdtypes.html

get(key[, default])
如果key在字典中,则返回key的值,否则返回默认值。如果未提供default,则默认为None,因此此方法永远不会引发KeyError。

setdefault(key[, default])
如果key在字典中,则返回其值。如果不是,请插入具有默认值的密钥,然后返回默认值。默认默认为无。