大佬来救救吧!关于python中对列表对象的问题

在python中为什么
列表对可以在for循环中被修改且不报错,
但字典集合在python被修改一次后就会报错。
我想知道他们在内存层面的缘由。哪位大佬来告诉我这个小白呀
先祭出一点代码:

a = list(range(9))

b = set(a)

c = {}.fromkeys(a, 0)
for i in a:
    # 经测试 可以在for循环里面修改列表a的值 但有可能会导致死循环,以至程序混乱不受控制。
    a.append(randint(1, 100))

    # 经测试 不可以在for循环里面修改集合b的值 ,一旦修改,情况和字典差不多。
    # b.add(randint(1, 100))

    # 经测试 不可以在for循环里面修改字典c的值 ,一旦修改,下次循环就会报错
    # c[str(randint(1, 100))] = randint(1, 100)

    print(f'容器对象 c 的内容为: {a}')
阅读 2.7k
4 个回答

我觉得这个问题还挺好的。

python3.6之前的DictObject在实现的时候,内在的数据结构是一个无序hashmap,而hashmap的数据是存在一个可变长数组里的(关于这点可以去找找其他文章)。我们在用for循环遍历的时候,其实是在遍历这个数组,而由于插入的顺序和遍历的顺序并不一致,如果我们在遍历数组过程中数组发生了变化,我们无法知道插入的数据是在当前迭代器位置的前还是后,这会导致循环的逻辑完全无法预测:插入在迭代器位置之后,会多循环一次,在当前位置之前,会循环不到。所以python在实现Dict的迭代器时做了以下限制:每次调用next方法时,检查迭代器上记录的数组大小和字典实际的大小是否一致,不一致则直接报错。

按照这个限制,我们可以得出,其实不是不可以在循环过程中更改Dict,而是不能在改变Dict之后继续调用next方法进行循环,所以下面的代码是可以运行的:

a = {1: 1}
for i in a:
    a[2] = 2
    break # 只要更改之后马上break掉,就不会触发下一次的next
print(a) # {1: 1, 2: 2}

python3.7之后的DictObject用一个辅助数组实现了有序的Dict(这个也可以去找下文章,不赘述了),不过这个限制仍然保留了下来。

可以简单理解为list是有序的, set和dict是无序的
任何在原对象上可能导致索引变更的循环操作都应避免, 可以单独copy一份用于循环

Python中,字典的底层是通过散列表或说哈希表实现的,不能一边for循环一边改变键值,而你如果打破沙锅问到底,可以看看哈希表的原理

不知道你用的什么版本的Python,我在 3.8.2下可以运行

from random import randint

a = list(range(9))
b = set(a)
c = {}.fromkeys(a, 0)

for i in a:
    # 经测试 不可以在for循环里面修改集合b的值 ,一旦修改,情况和字典差不多。
    b.add(randint(1, 100))
    print(f'b: {b}')

    # 经测试 不可以在for循环里面修改字典c的值 ,一旦修改,下次循环就会报错
    c[str(randint(1, 10))] = randint(1, 100)
    print(f'c: {c}')

输出

b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 18}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 18}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 73}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 18, 50}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 73, '6': 12}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 14, 18, 50}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 73, '6': 12, '3': 90}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 14, 18, 50}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 73, '6': 12, '3': 90, '4': 43}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 64, 14, 18, 50}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 9, '6': 12, '3': 90, '4': 43}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 64, 14, 18, 50, 51}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 84, '5': 9, '6': 12, '3': 90, '4': 43, '7': 55}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 64, 70, 14, 18, 50, 51}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 26, '5': 9, '6': 12, '3': 90, '4': 43, '7': 55}
b: {0, 1, 2, 3, 4, 5, 6, 7, 8, 39, 64, 70, 14, 18, 50, 51, 22}
c: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, '2': 26, '5': 9, '6': 12, '3': 90, '4': 43, '7': 55, '8': 18}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏