2017-12-05 7 views
0

dictからサブクラス化されたオブジェクトのキーを反復処理すると、Python 3.6.3で奇妙な動作をしました。 dict.keys()の動作がPython 2からPython 3に変更されて、dict_keysオブジェクトを返すようになったので、見た目の浅いコピーの動作がわかります。私が理解できないのは、dict_keysの要素の1つが明示的な検索で検出されるが、dict_keysオブジェクトを反復するときには表示されない理由です。実験1では`dict_keys`を反復処理するときに、サブクラス化されたdictからキーが消えるのはなぜですか?

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} 

は、要素'Ar'が明示的にraw_chemsでそれを検索することによって発見された:

#!/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() 

そして、ここでは結果である:

はここMWEです。ただし、raw_chemsを反復すると、要素'Ar'は表示されません("Checking Ar"は表示されません)。

テスト2では、raw_chemsがタプルにキャストされ、"Checking Ar"が予想通りに表示されます。

また、テスト1では、'Ar'のキーは、テスト2と同じように'_Ar'に置き換えられていません。'Ar'は、テスト1の下線が付けられていない唯一のキーです。

私はこの問題の原因で2つの推定を持っている:dict_keysが持っているいくつかの明らかツー誰も - しかし、私行動や私がdictをサブクラス化していますので、いくつかのすごみが起こっています。おそらく2つの組み合わせ。

そして、誰かが無償で私が求めている質問を変更しようとする前に、私が解決しようとしているビッグ・ピクチャーの問題は、標準的な形式(HHH2にdictのキーの名前を変更することで、molecular hydrogenすべてが同じものを表しています)。そのアドレスにはいくつかのためのより興味深い問題であるかもしれないが代わりにの述べたように、私はそうXに焦点を当ててください、消滅キーについて聞いてるのよYあなたは...想像:)

答えて

0

でpython 3 dict.keys()は、辞書キーが変更された場合の実際のキーと変更に関するビューです。キーを反復処理する場合は、辞書のキーを変更しないでください。

0

dict.keys() Python 3では、辞書のキーにビューが返されます。辞書のキーを変更すると、dict.keys()の内容も変更されます。これはちょうどあなたがリストを反復処理しながら、項目を削除するときのように、スキップされたアイテムにつながることができます:

あなたがO2を処理しようとしていると仮定します

N2 
--> O2 
    Ar 
    CO2 

あなたがそれを処理し、O2を削除し、_O2を追加します。このプロセスでは、ArO2を含むスロットに挿入されます。

N2 
--> Ar 
    CO2 
    _O2 

今、あなたは、次の項目に「現在の項目」ポインタを進め、ループの次の繰り返しに進みます

N2 
    Ar 
--> CO2 
    _O2 

あなたはArを単にスキップされました。

解決策は1つあります。キーを反復処理する前にコピーを作成してください。別の解決策は、既存の辞書を変更するのではなく、新しい辞書を構築することです。私は一般的に、この方法をお勧めします。これは、プロセスでコードをきれいにすることができるからです。

第3の解決策は、リストを逆順に反復して、処理するために残したアイテムが削除されたときに移動しないようにすることです。ただし、辞書キーは特定の順序であることが保証されていません。それらはあなたが使用しているPythonのバージョンにありますが、それに頼るべきではありません。

+0

これはよく説明されていますが、あなたのロジックに従って、なぜO2もスキップされませんか?この場合、処理される最初の要素はN2です。ではなぜArだけがスキップされますか? – scharette

+0

'dict'実装に依存して、いくつかの状況で実際に同じスロットに入る可能性があるため、私は少し単純化しました。要点は、それらを反復処理している間は、確実にdictsを変更することはできません。注意が必要な場合はリストを使用することは可能ですが、明細書の注文については何も保証しません。 – kindall

+0

本質的に、 'dict_keys'の下にあるデータ構造は、' dict'を変更することによって、ユーザーとコードの間の契約に違反して、乱雑になります。新しい 'dict'を構築する際の問題は、' dict'がオブジェクト内から置き換え可能ではないように見える 'self'です。私はそれを最初に試してみました。そして、 'dict'エントリを置き換えることに戻りました。代替案は、恐ろしい内部プログラミングかひどいAPIのいずれかに見える。 – arclight

関連する問題