python dict:get vs setdefault

时间:2011-09-14 21:52:20

标签: 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)

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

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

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

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

8 个答案:

答案 0 :(得分:22)

您的两个示例执行相同的操作,但这并不意味着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)

得到了

0.794723378711
0.811882272256
0.724429205999
0.722129751973

为此,setdefaultget快10%左右。

get方法允许您使用setdefault执行 less 。您可以使用它来避免在密钥不存在时(如果那是经常发生的事情)获得KeyError,即使您不想设置密钥。

有关这两种方法的更多信息,请参阅Use cases for the 'setdefault' dict methoddict.get() method returns a pointer

关于setdefault的帖子得出的结论是,大部分时间,您都希望使用defaultdict。关于get的线程得出的结论是它很慢,并且通常你会更好(快速)做双重查找,使用defaultdict或处理错误(取决于字典的大小和你的用例) )。

答案 1 :(得分:14)

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

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

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

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

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

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

实际上比错误的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]

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

如果可以,我会说使用变体3,变量4作为defaultdict不合适的偶然地方的选项。避免使用两种原始版本。

答案 2 :(得分:10)

您可能希望查看defaultdict模块中的collections。以下内容与您的示例相同。

from collections import defaultdict

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

d = defaultdict(list)

for k, v in data:
    d[k].append(v)

还有更多here

答案 3 :(得分:3)

1。这里用一个很好的例子来解释:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/

  

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

     

dict。获取典型用法
  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在字典中,则返回其值。如果没有,插入密钥的值为default并返回默认值。默认默认为无。

答案 4 :(得分:1)

In [1]: person_dict = {}

In [2]: person_dict['liqi'] = 'LiQi'

In [3]: person_dict.setdefault('liqi', 'Liqi')
Out[3]: 'LiQi'

In [4]: person_dict.setdefault('Kim', 'kim')
Out[4]: 'kim'

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}

In [8]: person_dict.get('Dim', '')
Out[8]: ''

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}

答案 5 :(得分:1)

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

场景1

root = {}
root.setdefault('A', [])
print(root)

场景2

root = {}
root.get('A', [])
print(root)

在方案1中,输出为{'A': []},在方案2中为{}

因此setdefault()在字典中设置了缺少的键,而get()仅提供了默认值,但没有修改字典。

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

使用setdefault()

def fn1(dic, key, lst):
    dic.setdefault(key, []).extend(lst)

使用get()

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

现在让我们检查时间-

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

花费288 ns

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

花费128 s

因此,这两种方法之间的时间差异很大。

答案 6 :(得分:1)

这个问题没有严格的答案。他们俩都达到了相同的目的。它们都可以用于处理键上的缺失值。我发现的唯一区别是,使用setdefault()时,您调用的键(如果以前不在字典中)会自动插入,而get()不会发生。这是一个例子: Setdefault()

>>> myDict = {'A': 'GOD', 'B':'Is', 'C':'GOOD'} #(1)
>>> myDict.setdefault('C')  #(2)
'GOOD'
>>> myDict.setdefault('C','GREAT')  #(3)
'GOOD'
>>> myDict.setdefault('D','AWESOME') #(4)
'AWESOME'
>>> myDict #(5)
{'A': 'GOD', 'B': 'Is', 'C': 'GOOD', 'D': 'AWSOME'} 
>>> myDict.setdefault('E')
>>>

获取()

>>> myDict = {'a': 1, 'b': 2, 'c': 3}   #(1)
>>> myDict.get('a',0)   #(2)
1
>>> myDict.get('d',0)   #(3)
0
>>> myDict #(4)
{'a': 1, 'b': 2, 'c': 3}

这是我的结论:对于默认值插补,没有一个具体的答案是哪个最合适。唯一的区别是setdefault()会自动在字典中添加具有默认值的任何新键,而get()不会。有关更多信息,请转到here

答案 7 :(得分:0)

dict.get的逻辑是:

if key in a_dict:
    value = a_dict[key] 
else: 
    value = default_value

举个例子:

In [72]: a_dict = {'mapping':['dict', 'OrderedDict'], 'array':['list', 'tuple']}
In [73]: a_dict.get('string', ['str', 'bytes'])
Out[73]: ['str', 'bytes']
In [74]: a_dict.get('array', ['str', 'byets'])
Out[74]: ['list', 'tuple']

setdefault的机制是:

    levels = ['master', 'manager', 'salesman', 'accountant', 'assistant']
    #group them by the leading letter
    group_by_leading_letter = {}
    # the logic expressed by obvious if condition
    for level in levels:
        leading_letter = level[0]
        if leading_letter not in group_by_leading_letter:
            group_by_leading_letter[leading_letter] = [level]
        else:
            group_by_leading_letter[leading_letter].append(word)
    In [80]: group_by_leading_letter
    Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

setdefault dict方法正是为了这个目的。前面的for循环可以重写为:

In [87]: for level in levels:
    ...:     leading = level[0]
    ...:     group_by_leading_letter.setdefault(leading,[]).append(level)
Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

这非常简单,意味着非空列表附加元素或空列表附加元素。

defaultdict,这使得这更容易。要创建一个,您可以传递一个类型或函数来为dict中的每个插槽生成默认值:

from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
    group_by_leading_letter[level[0]].append(level)