为什么 setdefault 在字典理解中不起作用?

Why doesn't setdefault work inside a dictionary comprehension?

为什么 setdefault 不会在字典推导中每次出现 a 时递增 1,而是在循环中递增?这里发生了什么?

替代解决方案很棒。我最感兴趣的是理解为什么这不起作用。

使用 setdefault 的循环有效

a = [1,1,2,2,2,3,3]

b = {}

for x in a:
    b[x] = b.setdefault(x, 0) + 1

b

Out[4]: {1: 2, 2: 3, 3: 2}

使用 setdefault 的字典理解不起作用

b = {k: b.setdefault(k, 0) + 1 for k in a}

b

Out[7]: {1: 1, 2: 1, 3: 1}

更新

感谢您的回答,我想尝试计时解决方案。

def using_get(a):
    b = {}
    for x in a:
        b[x] = b.get(x, 0) + 1
    return b


def using_setdefault(a):
    b = {}
    for x in a:
        b[x] = b.setdefault(x, 0) + 1
    return b


timeit.timeit(lambda: Counter(a), number=1000000)
Out[3]: 15.19974103783569

timeit.timeit(lambda: using_get(a), number=1000000)
Out[4]: 3.1597984457950474

timeit.timeit(lambda: using_setdefault(a), number=1000000)
Out[5]: 3.231248461129759

字典理解中还没有字典。您正在构建一个全新的字典,替换 b 之前绑定的任何内容。

也就是说,在你的字典理解中,b.setdefault()是一个完全不同的字典,它与理解所构建的对象无关。

事实上,只有当 b 在 运行 表达式之前使用 .setdefault() 方法绑定到一个对象时,您的字典理解才有效。如果 b 尚未定义,或未使用此类方法绑定到对象,则它会失败并出现异常:

>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined
>>> b = 42
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
AttributeError: 'int' object has no attribute 'setdefault'

你不能用字典理解来做你想做的,除非你把你的数字分组,这需要排序和 itertools.groupby();这不是一种有效的方法(需要 O(NlogN) 步而不是 O(N)):

>>> from itertools import groupby
>>> {k: sum(1 for _ in group) for k, group in groupby(sorted(a))}
{1: 2, 2: 3, 3: 2}

请注意,标准库已经附带了一个计数工具;请参阅 collections.Counter() object:

>>> from collections import Counter
>>> Counter(a)
Counter({2: 3, 1: 2, 3: 2})

实际上,如果您在一个干净的命名空间(没有事先定义 b 的命名空间)中尝试,您的第二个代码片段会引发 NameError

bruno@bigb:~/Work/playground$ python
Python 2.7.3 (default, Jun 22 2015, 19:33:41) 
>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined

这应该会提示您哪里出了问题。

声明:

b = {k: b.setdefault(k, 0) + 1 for k in a}

first 评估(好吧,实际上试图...)右侧表达式 {k: b.setdefault(k, 0) + 1 for k in a}then 将结果绑定到名称 b

如果在计算表达式时未定义 b,您会得到上述异常(当然)。如果它被定义并绑定到字典(或任何具有 setdefault(x, y) 方法的 FWIW),您将得到在此时 b 绑定到的任何内容上调用 setdefault() 的结果。

这不起作用,因为 b 在字典理解完成之前没有定义。通常,您应该为此得到一个 NameError;如果不是,那是因为您之前已经定义了 b,但这将是一个不同的字典。

话虽如此:看来你可以用collections.Counter来完成这个。

>>> a = [1,1,2,2,2,3,3]
>>> collections.Counter(a)
Counter({2: 3, 1: 2, 3: 2})