-2

为什么 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
4

3 回答 3

4

字典理解中还没有字典。您正在构建一个全新的字典,替换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()对象

>>> from collections import Counter
>>> Counter(a)
Counter({2: 3, 1: 2, 3: 2})
于 2015-09-28T10:12:36.090 回答
2

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

话虽如此:看来您可以仅用collections.Counter于此目的。

>>> a = [1,1,2,2,2,3,3]
>>> collections.Counter(a)
Counter({2: 3, 1: 2, 3: 2})
于 2015-09-28T10:19:21.643 回答
2

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

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}

首先评估(好吧,实际上是尝试...)右侧的表达式{k: b.setdefault(k, 0) + 1 for k in a}然后将结果绑定到名称b

如果b在表达式被 eval 时没有定义,你会得到上面的异常(当然)。如果它已定义并绑定到 dict(或任何具有 setdefault(x, y)FWIW 方法的东西),您将获得调用setdefault()此时b绑定的任何内容的结果。

于 2015-09-28T10:16:56.277 回答