【问题标题】:Iterating over a changing dictionary迭代不断变化的字典
【发布时间】:2014-11-21 12:18:17
【问题描述】:

我有一段 python 代码,它需要迭代字典中的所有键,其中循环的主体可能会改变字典。

只是想迭代字典给我RuntimeError: dictionary changed size during iteration。搜索错误消息将我带到another question,建议的解决方案是在开始迭代之前复制键列表。

在我的场景中,这只是部分答案,因为我不仅需要迭代原始键,还需要迭代在迭代时添加的键。我需要只有在没有更多键被添加到字典时才会终止的代码,并且它已经处理了字典中已经存在的任何键。

到目前为止,我想出了这个:

processed = set()
while True:
    keys = set(dictionary) - processed
    if not keys:
        break
    for k in keys:
        processed.add(k)
        # Do something with k

这种方法似乎过于复杂。有什么办法可以简化它,并且仍然让它处理所有添加到字典中的键,而不用多次处理它们?

在其他一些语言中,while 循环的前四行可以简单地写成:

while keys = set(dictionary) - processed:

但是,这在 python 中不起作用。我发现a question 在 python 的 while 循环中使用赋值作为条件。但建议的答案似乎都不适用于我的场景。

【问题讨论】:

    标签: python


    【解决方案1】:

    考虑相反的方法。跟踪仍然需要处理的密钥,而不是已经处理的密钥。

    remaining = set(dictionary.items())
    
    while remaining:
        key, value = remaining.pop()
    
        # process the item
    
        if item_to_add:
            dictionary[new_key] = new_value
            remaining.add((new_key, new_value))
    

    这避免了每次迭代都进行昂贵的集差操作。

    如果您真的不知道在处理过程中添加了哪些键,那么您问题中的代码就可以了。稍微不同的写法是:

    keys = set(dictionary)
    processed = set()
    
    while keys:
        for k in keys:
            # Do something with k
    
        processed.update(keys)
        keys = set(dictionary) - processed
    

    这样更好吗?更差?我想,由你决定。

    【讨论】:

    • 我的循环体没有明确地将键添加到字典中。相反,它调用另一个函数,该函数可能会或可能不会向字典添加键。所以我的循环体可以知道添加了哪些键的唯一方法是查看当前的键集。
    • 反复重新检查字典以查看更改的内容可能代价高昂。有什么方法可以重构代码,以便您知道添加了哪些项目?如果不是,那么您的原始代码对我来说看起来不错。
    • 为了让循环体知道要添加什么而重构代码对我来说是不切实际的。在我的情况下,集合差异的性能不会成为问题,因为处理单个密钥的成本远高于计算集合差异的成本。
    • 感谢您提供解决问题的不同方法。如果没有完全通用的解决方案,最好有多个想法,每个想法可能适用于不同的场景。
    【解决方案2】:

    也许您可以改用OrderedDict。迭代时添加键没问题

    >>> from collections import OrderedDict
    >>> d = OrderedDict([(1, 2), (3, 4)])
    >>> for k in d:
    ...     if k == 1:d[5] = 6
    ...     print(k)
    ... 
    1
    3
    5
    

    您是在更新密钥还是只是添加密钥?如果更新,您需要指定是否应再次处理密钥

    【讨论】:

    • 这听起来可能是一个更清洁的解决方案。该文档指出,迭代将按照添加键的顺序处理键。但是,它没有说明是否保证在迭代完成之前处理迭代时添加的键。我不更改现有密钥,我只添加和删除密钥。
    【解决方案3】:

    我将代码改写为:

    processed = set()
    while set(dictionary) - processed:
        for k in set(dictionary) - processed:
            processed.add(k)
            # Do something with k
    

    这确实复制了set(dictionary) - processed 表达式,但仍然使代码更易于阅读。

    【讨论】:

      猜你喜欢
      • 2022-11-27
      • 2012-02-28
      • 1970-01-01
      • 1970-01-01
      • 2023-01-21
      • 1970-01-01
      • 1970-01-01
      • 2017-03-02
      • 2013-02-01
      相关资源
      最近更新 更多