繁体   English   中英

python dict:get vs setdefault

[英]python dict: get vs 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”的值,如果不可用,则在字典中输入“[]”,返回对该值或“[]”的引用,并将“val”附加到该值参考。 虽然肯定很流畅,但它至少不直观(至少对我而言)。

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

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

你的两个例子做同样的事情,但这并不意味着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 即使您不想设置密钥,您也可以使用它来避免在密钥不存在时出现KeyError (如果这是经常发生的事情)。

有关这两种方法的更多信息,请参阅“setdefault”dict 方法dict.get() 方法返回一个指针的用例

关于setdefault的线程得出的结论是,大多数情况下,您希望使用defaultdict 关于get的线程得出结论,它很慢,而且通常最好(速度明智)进行双重查找、使用 defaultdict 或处理错误(取决于字典的大小和您的用例)。

来自 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不完全合适的偶尔地方。 避免使用两种原始变体。

您可能想查看collections模块中的defaultdict 以下相当于您的示例。

from collections import defaultdict

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

d = defaultdict(list)

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

还有更多的在这里

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

场景一

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

场景 2

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

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

所以setdefault()在 dict 中设置不存在的键,而get()只为您提供默认值但它不会修改字典。

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

使用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 纳秒

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

花了 128 秒

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

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])
如果键在字典中,则返回键的值,否则返回默认值。 如果未给出默认值,则默认为 None,因此此方法永远不会引发 KeyError。

setdefault(key[, default])
如果键在字典中,则返回其值。 如果没有,插入值为 default 的并返回默认值。 默认默认为无。

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 ,这使得这更容易。 要创建一个,你传递一个类型或函数来为字典中的每个插槽生成默认值:

from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
    group_by_leading_letter[level[0]].append(level)
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'}

这个问题没有严格的答案。 它们都实现了相同的目的。 它们都可以用来处理键上的缺失值。 我发现的唯一区别是,使用 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() 不会。 欲了解更多信息,请到这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM