【问题标题】:Why does key disappear from subclassed dict when iterating over `dict_keys`?为什么在迭代`dict_keys`时键从子类dict中消失?
【发布时间】:2017-12-05 19:59:11
【问题描述】:

在迭代从dict 子类化的对象的键时,我在 Python 3.6.3 中遇到了奇怪的行为。我已经了解 dict.keys() 的行为从 Python 2 更改为 Python 3 以返回 dict_keys 对象,因此看到的浅拷贝行为是可以理解的。我不明白的是为什么 dict_keys 中的元素之一可以通过显式搜索找到,但在迭代 dict_keys 对象时不会出现。

这是 MWE:

#!/usr/bin/env python

from __future__ import print_function

class chemdist(dict):
    def normalize_names(self, force_static=False):

        if force_static:
            raw_chems = tuple(self.keys())
        else:
            raw_chems = self.keys()

        print("raw_chems is " + repr(raw_chems))

        if 'Ar' in raw_chems:
            print("Found Argon!")

        for old_chem in raw_chems:
            print("Checking {:s}".format(old_chem))
            if old_chem.startswith('_'):
                new_chem = old_chem
            else:
                new_chem = '_' + old_chem

            if old_chem != new_chem:
                curr_keys = self.keys()
                if new_chem in curr_keys:
                    self[new_chem] += self[old_chem]
                else:
                    self[new_chem] = self[old_chem]

                del self[old_chem]

def main():
    h_dryair = {
        'N2' : 78.084,
        'O2' : 20.947,
        'Ar' :  0.934,
        'CO2' : 0.0350
    }

    print("Test 1: Not static")
    cd_dryair = chemdist(h_dryair)

    print("  Unnormalized:")
    print(cd_dryair)

    print("\n  Normalizing:")
    cd_dryair.normalize_names()

    print("\n  Normalized:")
    print(cd_dryair)

    print("\n\nTest 2: Static")
    cd_dryair = chemdist(h_dryair)

    print("  Unnormalized:")
    print(cd_dryair)

    print("\n  Normalizing:")
    cd_dryair.normalize_names(force_static=True)

    print("\n  Normalized:")
    print(cd_dryair)

main()

结果如下:

Test 1: Not static
  Unnormalized:
{'N2': 78.084, 'O2': 20.947, 'Ar': 0.934, 'CO2': 0.035}

  Normalizing:
raw_chems is dict_keys(['N2', 'O2', 'Ar', 'CO2'])
Found Argon!
Checking N2
Checking O2
Checking CO2
Checking _N2
Checking _O2
Checking _CO2

  Normalized:
{'Ar': 0.934, '_N2': 78.084, '_O2': 20.947, '_CO2': 0.035}


Test 2: Not static
  Unnormalized:
{'N2': 78.084, 'O2': 20.947, 'Ar': 0.934, 'CO2': 0.035}

  Normalizing:
raw_chems is ('N2', 'O2', 'Ar', 'CO2')
Found Argon!
Checking N2
Checking O2
Checking Ar
Checking CO2

  Normalized:
{'_N2': 78.084, '_O2': 20.947, '_Ar': 0.934, '_CO2': 0.035}

在测试 1 中,通过在 raw_chems 中显式搜索找到元素 'Ar'。然而,当迭代raw_chems 时,元素'Ar' 永远不会出现("Checking Ar" 永远不会出现)。

在测试 2 中,raw_chems 被强制转换为元组,"Checking Ar" 按预期出现。

另外,比较最终的标准化对象内容:在测试 1 中,'Ar' 键没有像在测试 2 中那样被替换为 '_Ar''Ar' 是测试 1 中唯一没有被替换的键以下划线为前缀。

我对这个问题的原因有两个猜测:dict_keys 有一些对每个人都很明显的行为,或者因为我正在继承 dict 而发生了一些奇怪的事情。可能是两者的结合。

在有人无偿尝试更改我要问的问题之前,我要解决的大问题是将 dict 键重命名为规范形式(HHH2molecular hydrogen都代表同一件事)。虽然这对于某些人来说可能是一个更有趣的问题,但我问的是消失的键,所以请关注 x ,而不是你想象的 y ... :)

【问题讨论】:

    标签: python python-3.x dictionary key


    【解决方案1】:

    在 python 3 中,dict.keys() 是关于实际键和更改的视图,如果字典键发生变化。如果您遍历键,则不得更改字典的键。

    【讨论】:

      【解决方案2】:

      dict.keys() 在 Python 3 下返回字典键的视图。当您更改字典的键时,您也更改了dict.keys() 的内容。这可能会导致项目被跳过,就像您在迭代列表时删除项目一样:

      假设您即将处理O2

          N2
      --> O2
          Ar
          CO2
      

      您处理它并删除O2 并添加_O2。在此过程中,Ar 滑入包含O2 的槽中。

          N2
      --> Ar
          CO2
          _O2
      

      现在您继续循环的下一次迭代,它将“当前项”指针前进到下一项:

          N2
          Ar
      --> CO2
          _O2
      

      你刚刚跳过了Ar

      如您所见,一种解决方案是:在对密钥进行迭代之前复制密钥。另一种解决方案是构建一个新字典而不是修改现有字典。我通常推荐这种方法,因为它可以使您的代码在此过程中更加干净。

      第三种解决方案是以相反的顺序迭代列表,这样当您删除一个项目时,您仍然需要处理的项目不会移动;但是,不保证字典键按任何特定顺序排列。尽管它们在您使用的 Python 版本中,但您不应该依赖它。

      【讨论】:

      • 这很好解释,但按照你的逻辑,为什么 O2 也没有跳过?在这种情况下,要处理的第一个元素是 N2。那么为什么只跳过 Ar 呢?
      • 我已经简化了一点,因为在某些情况下该项目实际上可能会进入同一个插槽,具体取决于dict 实现。关键是,您不能在迭代它们时可靠地更改字典。如果你小心的话,列表是可能的,但是dicts不能保证项目的顺序。
      • 基本上dict_keys 下的数据结构通过更改dict 被破坏,违反了用户和代码之间的约定。构建一个新的dict 的问题是这里的dictself,它似乎不能从对象内替换。我先是凭直觉尝试了这个,然后又退回到替换 dict 条目。替代方案似乎是糟糕的内部编程或糟糕的 API。
      • 在我的用例中,dict 的顺序并不重要,否则我会使用collections.OrderedDict。主要问题是我打算在我要求它们时获取dict 键的快照,而不是在 Python 想把它们给我的时候。这对其他用例非常有用,但不是这个。
      • 很容易冻结快照,然后将其转换为列表(或您所做的元组)。
      猜你喜欢
      • 2021-04-29
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-30
      • 2021-05-09
      • 2018-08-17
      相关资源
      最近更新 更多