反復中にPythondictを変更する


87

Python辞書がありd、次のように繰り返し処理しているとします。

for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

fそして、これgは単なるブラックボックス変換です。)

つまり、をd使用してアイテムを繰り返し処理しながら、アイテムを追加/削除しようとしiteritemsます。

これは明確に定義されていますか?あなたの答えを裏付けるためにいくつかの参考文献を提供できますか?

(壊れた場合にこれを修正する方法はかなり明白なので、これは私が求めている角度ではありません。)




私はこれを試みましたが、最初のdictサイズを変更しない場合(たとえば、キー/値を削除する代わりに置き換える場合)、このコードは例外をスローしないようです
Artsiom Rudzenka 2011

このトピックを検索しているすべての人(私を含む)にとって、「壊れた場合にこれを修正する方法はかなり明白」であることに同意しません。受け入れられた回答が少なくともこれに触れていればよかったのですが。
アレックス・ピーターズ

回答:


53

Pythonドキュメントページ(Python 2.7の場合)で明示的に言及されている

iteritems()辞書のエントリを追加または削除するときに使用RuntimeErrorすると、すべてのエントリの反復が発生するか、失敗する可能性があります。

同様のためのPython 3

同じことが成り立つiter(d)d.iterkeys()d.itervalues()、と私はのためにそれがないことを言って限り行くよfor k, v in d.items():(私は正確に何を覚えていないことができforないが、実装が呼び出された場合、私は驚かないだろうiter(d))。


48
まさにコードスニペットを使用したことを述べて、コミュニティのために恥ずかしい思いをします。RuntimeErrorが発生しなかったので、すべてが良かったと思いました。そして、それはしばらくの間でした。肛門性格の単体テストは私に親指を立ててくれました、そしてそれがリリースされたときさえそれはうまく動いていました。それから、私は奇妙な行動を取り始めました。何が起こっていたのかというと、辞書の項目がスキップされていたため、辞書のすべての項目がスキャンされていなかったのです。子供たち、私が人生で犯した過ちから学び、ただノーと言ってください!;)
Alan Cabrera

3
現在のキーの値を変更している場合(ただし、キーを追加または削除していない場合)、問題が発生する可能性はありますか?これで問題が発生することはないと思いますが、知りたいのですが!
Gershom 2015

@GershomMaes私は何も知りませんが、ループ本体が値を利用し、値が変更されることを期待していない場合は、まだ地雷原にぶつかっている可能性があります。
ラファエルサンピエール

3
d.items()Python 2.7では安全である必要があります(ゲームはPython 3で変更されdます)。これは、本質的にのコピーを作成するため、繰り返し処理する内容を変更する必要がないためです。
ポール価格

これがためにも真実であるかどうかは知ることは興味深いものになるだろうviewitems()
jlhが

50

アレックス・マルテッリはここでこれを検討します

コンテナをループしているときにコンテナを変更する(dictなど)のは安全ではない場合があります。したがってdel d[f(k)]、安全ではない可能性があります。ご存知のように、回避策はd.items()d.iteritems()(同じ基になるコンテナーを使用する)の代わりに(コンテナーの独立したコピーをループする)を使用することです。

dictの既存のインデックスの値を変更することは問題d[g(k)]=vありませんが、新しいインデックス(例)に値を挿入すると機能しない場合があります。


3
これが私にとって重要な答えだと思います。多くのユースケースでは、1つのプロセスで物を挿入し、別のプロセスで物をクリーンアップ/削除するため、d.items()を使用するためのアドバイスが機能します。Python 3の警告に耐えられない
easytiger 2013

4
Pythonの3警告の詳細については、に見出すことができるPEP 469前述のPython 2辞書メソッドの意味的な等価物が挙げられます。
ライオネルブルックス

1
「dictの既存のインデックスの値を変更しても大丈夫です -これについての参照はありますか?
JonathonReinhart19年

1
@JonathonReinhart:いいえ、これについてのリファレンスはありませんが、Pythonではかなり標準的だと思います。たとえば、Alex MartelliはPythonコア開発者であり、ここでその使用法示しています
unutbu

27

少なくともd.iteritems()。ではそれを行うことはできません。私はそれを試しました、そしてPythonはで失敗します

RuntimeError: dictionary changed size during iteration

代わりにを使用するとd.items()、機能します。

Python 3では、Python 2d.items()と同様d.iteritems()に、辞書へのビューです。Python3でこれを行うには、代わりにd.copy().items()。これにより、繰り返し処理するデータ構造の変更を回避するために、辞書のコピーを繰り返し処理することもできます。


2
答えにPython3を追加しました。
murgatroid99 2013年

2
参考までに2to3、Py2d.items()からPy3への直訳(たとえば、によって使用される)はですがlist(d.items())d.copy().items()おそらく同等の効率です。
セーレンLøvborg

2
dictオブジェクトが非常に大きい場合、d.copy()。items()は効率的ですか?
トンボ

11

私はNumpy配列を含む大きな辞書を持っているので、@ murgatroid99によって提案されたdict.copy()。keys()は実行可能ではありませんでした(それは機能しましたが)。代わりに、keys_viewをリストに変換しただけで、正常に機能しました(Python 3.4の場合)。

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

これは、上記の回答のようにPythonの内部動作の哲学的領域に飛び込むことはありませんが、述べられた問題に対する実用的な解決策を提供します。


6

次のコードは、これが明確に定義されていないことを示しています。

def f(x):
    return x

def g(x):
    return x+1

def h(x):
    return x+10

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[g(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[h(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

最初の例はg(k)を呼び出し、例外をスローします(反復中に辞書のサイズが変更されました)。

2番目の例では、h(k)を呼び出して例外をスローしませんが、次のように出力します。

{21: 'axx', 22: 'bxx', 23: 'cxx'}

コードを見ると、これは間違っているように見えます-私は次のようなものを期待していました:

{11: 'ax', 12: 'bx', 13: 'cx'}

なぜあなたが期待するのか理解できます{11: 'ax', 12: 'bx', 13: 'cx'}が、21、22、23は実際に何が起こったのかについての手がかりを与えるはずです:あなたのループはアイテム1、2、3、11、12、13を通過しましたが、2番目を拾うことができませんでしたすでに繰り返し処理したアイテムの前に挿入された新しいアイテムのラウンド。h()戻るように変更するx+5と、別のx:'axxx'などまたは「x + 3」が表示され、壮大なものが表示されます'axxxxx'
Duncan

ええ、私の間違いは私が恐れています-私の期待される出力は{11: 'ax', 12: 'bx', 13: 'cx'}あなたが言った通りだったので、それについての私の投稿を更新します。いずれにせよ、これは明らかに明確に定義された動作ではありません。
コンバットデイブ2011

1

同じ問題が発生し、次の手順を使用してこの問題を解決しました。

Pythonリストは、反復中に変更した場合でも反復できます。したがって、次のコードでは、1が無限に出力されます。

for i in list:
   list.append(1)
   print 1

したがって、listとdictを共同で使用すると、この問題を解決できます。

d_list=[]
 d_dict = {} 
 for k in d_list:
    if d_dict[k] is not -1:
       d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted)
       d_dict[g(k)] = v # add a new item 
       d_list.append(g(k))

反復中にリストを変更しても安全かどうかはわかりません(場合によっては機能する可能性があります)。たとえば、この質問を参照してください...
ローマ

@Romanリストの要素を削除する場合は、通常の順序では次の要素のインデックスが削除時に変更されるため、逆の順序で安全に繰り返すことができます。この例を参照してください。
mbomb007 2018

1

Python 3あなたはただすべきです:

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

または使用:

t2 = t1.copy()

元の辞書を変更しないでください。混乱を招き、潜在的なバグやRunTimeErrorsが発生します。新しいキー名で辞書に追加するだけでない限り。


0

今日も同様のユースケースがありましたが、ループの開始時に辞書のキーを単純に実体化するのではなく、dictを変更して、順序付けられたdictであるdictの反復に影響を与えたいと思いました。

私は最終的に次のルーチンを作成しました。これはjaraco.itertoolsにもあります。

def _mutable_iter(dict):
    """
    Iterate over items in the dict, yielding the first one, but allowing
    it to be mutated during the process.
    >>> d = dict(a=1)
    >>> it = _mutable_iter(d)
    >>> next(it)
    ('a', 1)
    >>> d
    {}
    >>> d.update(b=2)
    >>> list(it)
    [('b', 2)]
    """
    while dict:
        prev_key = next(iter(dict))
        yield prev_key, dict.pop(prev_key)

docstringは使用法を示しています。d.iteritems()上記の代わりにこの機能を使用して、目的の効果を得ることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.