为什么dict.get(key)能够运行,但dict[key]不能?

19

我正在尝试根据字符串中有多少个1,将某些数字的二进制字符串分组。

这种方式不起作用:

s = "0 1 3 7 8 9 11 15"
numbers = map(int, s.split())
binaries = [bin(x)[2:].rjust(4, '0') for x in numbers]

one_groups = dict.fromkeys(range(5), [])
for x in binaries:
    one_groups[x.count('1')] += [x]

需要的预期字典是 one_groups

{0: ['0000'], 
 1: ['0001', '1000'], 
 2: ['0011', '1001'], 
 3: ['0111', '1011'], 
 4: ['1111']}

但是我收到了

{0: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 1: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 2: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 3: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 4: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111']}

到目前为止,唯一有效的方法是使用 one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x] 而不是 one_groups[x.count('1')] += [x]。为什么会这样呢?如果我记得正确,dict[key] 应该返回字典的值,就像 dict.get(key) 的工作原理一样。我看过这个线程 Why dict.get(key) instead of dict[key]?,但它没有回答我的问题,因为我确定程序不能得到 KeyError。我还尝试过 one_groups[x.count('1')].append(x),但这也行不通。

8
如果键不存在,get方法会返回None或者提供的默认值,而索引运算符[]则会在键不存在时抛出一个错误。 - adnanmuttaleb
附注:bin(x)[2:].rjust(4, '0') 可以简化为 '{:0>4b}'.format(x) - wjandrea
1
顺便提一下,制作一个 [mre] 会有帮助。在这种情况下,如何制作“二进制文件”与问题无关,因此您可以直接提供其值。 - wjandrea
1
这个回答解决了你的问题吗?dict.fromkeys all point to same list - Georgy
3个回答

24
问题在于可变性: one_groups = dict.fromkeys(range(5), []) - 这段代码将同一个列表作为值传递给所有键。因此,如果您更改一个值,则会更改所有值。
这基本上相当于说:
tmp = []
one_groups = dict.fromkeys(range(5), tmp)
del tmp

如果你想使用一个新列表,你需要在循环中完成 - 可以使用显式的for循环或字典推导式:

one_groups = {key: [] for key in range(5)}

这个东西将会对每个键执行[](相当于list()),从而使得不同列表有不同的值。


get是如何工作的?因为你明确地获取了当前列表,但是+会创建一个新的结果列表。无论是one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]还是one_groups[x.count('1')] = one_groups[x.count('1')] + [x]都没有关系——重要的是有+

我知道每个人都说a+=b就是a=a+b,但是为了优化实现可能不同——在列表的情况下,+=只是.extend,因为我们知道我们想要我们的结果在当前变量中,所以创建新列表会浪费内存。


啊,好的,我明白了。我也记得当我想使用“mylist = [[] * 5] * 5”创建一个二维列表时遇到了类似的问题,以及如何使用“mylist = [[] for x in range(5)] * 5”来解决它。只是为了快速澄清,从我的理解来看,这是因为变量指向那个空列表的内存地址。这是否意味着如果我使用原始类型而不是列表就不会出现这个问题? - SpectraXCD
1
是的,如果您使用原始类型,这将解决问题,但会破坏one_groups[x.count('1')] += [x],因为您无法将列表添加到原始类型中。更好的解决方案是改用defaultdict。 - Fakher Mokadem
4
特别地,+ 调用 __add__ 并返回一个新对象,而 += 调用 __iadd__,不要求返回一个新对象。 - njzk2

8
问题在于使用了one_groups = dict.fromkeys(range(5), []) (这会将同一个列表作为值传递给所有键。所以如果你改变一个值,所有的值都会改变)
你可以使用以下代码:one_groups = {i:[] for i in range(5)} (这将为每个键“执行”[](即等于list()),从而使得值带有不同的列表。)

6
你说得完全正确,尽管解释会更有帮助。区分这两行的不同之处并不显然。 - AnsFourtyTwo
是的,这是我的错。抱歉。 - Hameda169

4
这是有关字典的fromkeys方法的帮助。
内置函数fromkeys的帮助信息如下: fromkeys(iterable, value=None, /) 是内置type实例的方法。创建一个新的字典,以iterable中的键作为键,并将值设置为value。
这意味着fromkeys将接受一个值,即使它是可调用的,它也将先进行评估,然后将该值分配给所有字典键。
在Python中,列表是可变的,因此它将分配相同的空列表引用,一个更改会影响它们所有。可以使用defaultdict来替代。
>>> from collections import defaultdict
>>> one_groups = defaultdict(list)
>>> for x in binaries:
      one_groups[x.count('1')] += [x]
>>> one_groups = dict(one_groups) # to stop default dict behavior

这将接受对不存在的键的分配,并且值将默认为空列表(在这种情况下)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接