字典在迭代过程中改变了大小——代码在 Py2 中工作,在 Py3 中不工作

新手上路,请多包涵

我有以下示例代码:

 k_list = ['test', 'test1', 'test3']

def test(*args, **kwargs):
    for k, value in kwargs.items():
        if k in k_list:
            print("Popping k = ", k)
            kwargs.pop(k, None)
    print("Remaining KWARGS:", kwargs.items())

test(test='test', test1='test1', test2='test2', test3='test3')

在 Python 2.7.13 中,这完全符合我的预期,并且在 kwargs 中仍然有一个项目:

 ('Popping k = ', 'test')
('Popping k = ', 'test1')
('Popping k = ', 'test3')
('Remaining KWARGS:', [('test2', 'test2')])

然而,在 Python 3.6.1 中,这失败了:

 Popping k =  test
Traceback (most recent call last):
  File "test1.py", line 11, in <module>
    test(test='test', test1='test1', test2='test2', test3='test3')
  File "test1.py", line 5, in test
    for k, value in kwargs.items():
RuntimeError: dictionary changed size during iteration

我需要调整什么以保持 Python 2 兼容性但在 Python 3.6 中正常工作?剩余的 kwargs 将用于我脚本中的后续逻辑。

原文由 PyNoob 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 595
2 个回答

它在 python2.x 中工作的原因是因为 kwargs.items() 创建了一个列表——您可以将其视为字典键值对的快照。由于它是一个快照,您可以在不修改您正在迭代的快照的情况下更改字典,一切正常。

在 python3.x 中, kwargs.items() 创建字典键值对的 _视图_。由于它是一个视图,因此您不能在不更改视图的情况下更改字典。这就是你在 python3.x 中出错的原因

一种适用于 python2.x 和 python3.x 的解决方案是始终使用 list 内置创建快照:

 for k, value in list(kwargs.items()):
    ...

或者,也可以通过复制字典来创建快照:

 for k, value in kwargs.copy().items():
    ...

这会起作用。在我用我的交互式解释器做的一个非常不科学的实验中,第一个版本比 python2.x 上的第二个版本快很多。另请注意,这整个事情在 python2.x 上会稍微低效,因为您将创建一些东西的附加副本( listdict 取决于您引用的版本) .根据您的其他代码,这看起来并不是什么大问题。如果是,您可以使用类似 six 的兼容性:

 for k, value in list(six.iteritems(kwargs)):
    ...

原文由 mgilson 发布,翻译遵循 CC BY-SA 3.0 许可协议

在 Python 3 中, dict.items 被更改为一个 _视图_,之前它返回一个副本。要再次使用副本并获得交叉兼容代码,您可以更改此行:

 for k, value in kwargs.items():

对此:

 for k, value in list(kwargs.items()):

原文由 wim 发布,翻译遵循 CC BY-SA 3.0 许可协议

推荐问题