【问题标题】:Why doesn't setdefault work inside a dictionary comprehension?为什么 setdefault 在字典理解中不起作用?
【发布时间】:2015-09-28 10:08:26
【问题描述】:

为什么 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 = collections.Counter(a)吗?
  • dict.setdefault 无论如何都是一种奇怪的方法——你通常会将它与可变值一起使用,而不是不可变值(例如 d.setdefault[x, []].append(...))。我会写b[x] = b.get(x, 0) + 1
  • 不起作用。实际上,这非常有效。如果b = {}。最初,您将获得一个新字典(新分配给b),其中包含a 的所有键值和= 0 + 1 的所有值。然而,原来的b 已经消失了。不过,这可能不是您所期望的。如果b 没有在理解之前定义,你会得到NameError

标签: python python-2.7 dictionary-comprehension setdefault


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    实际上,如果您在干净的命名空间(没有事先定义 b 的命名空间)中尝试,您的第二个 sn-p 会引发 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},然后然后 将结果绑定到名称b

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

    【讨论】:

      【解决方案3】:

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

      话虽如此:看来您可以为此使用collections.Counter

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-07-31
        • 1970-01-01
        • 2021-07-01
        • 2015-05-29
        • 2019-03-29
        • 1970-01-01
        • 2021-10-20
        相关资源
        最近更新 更多