71

以下两个表达式似乎与我等价。哪一个更可取?

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 版虽然对没有经验的人来说更难掌握,但速度更快,因此更可取。意见?

4

8 回答 8

38

你的两个例子做同样的事情,但这并不意味着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方法允许您做的比您能做的setdefaultKeyError即使您不想设置密钥,也可以使用它来避免在密钥不存在时(如果这种情况经常发生)得到 a 。

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

关于线程的setdefault结论是,大多数情况下,您想使用defaultdict. 关于线程的get结论是它很慢,并且通常最好(速度方面)进行双重查找,使用默认字典或处理错误(取决于字典的大小和您的用例)。

于 2011-09-14T22:14:40.203 回答
26

来自 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 慢一点.

defaultdict如果可以的话,我会说使用变体 3,对于那些不完全适合的偶尔地方,可以选择变体 4 。避免两个原始变体。

于 2011-09-15T08:03:43.820 回答
14

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

情景一

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

情景二

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 秒

所以这两种方法之间有很大的时间差异。

于 2018-09-25T12:23:32.687 回答
11

您可能想defaultdictcollections模块中查看。以下等效于您的示例。

from collections import defaultdict

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

d = defaultdict(list)

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

这里还有更多。

于 2011-09-14T21:56:35.147 回答
8

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

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

于 2014-03-11T13:06:37.520 回答
2

的逻辑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)
于 2018-04-18T01:52:11.950 回答
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'}
于 2015-06-17T02:47:15.307 回答
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() 不会。欲了解更多信息,请到这里

于 2020-04-15T06:50:51.343 回答