为什么 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})
为什么 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})