繰り返しながら辞書から項目を削除する方法は?


295

繰り返しながらPythonの辞書から項目を削除することは正当ですか?

例えば:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

アイデアは、繰り返される辞書のサブセットである新しい辞書を作成する代わりに、特定の条件を満たさない要素を辞書から削除することです。

これは良い解決策ですか?よりエレガントで効率的な方法はありますか?


1
非常に興味深い回答が含まれる関連質問:stackoverflow.com/questions/9023078/…
最大

簡単に試すことができたでしょう。失敗した場合、それは正当ではありません。
Trilarion、2015

26
@Trilarion Oneは簡単に試すことができた... そして価値のないものを簡単に学んだ。成功したとしても、それ必ずしも正当なことではありません。エッジケースと予期しない警告がたくさんあります。この質問は、すべてのPythonistaであろうすべての人にとって重要な関心事です。「手軽に挑戦できたかも!」のオーダーで手を振って解雇!役に立たず、stackoverflow照会の探究心に反します。
セシルカレー

マックス関連質問を熟読した後、私は同意する必要があります。おそらく、その邪魔なほど詳細な質問とそのよく書かれた回答を代わりに熟読したいと思うでしょう。あなたのパイソンの心は吹き飛ばされます。
セシルカレー

1
@CecilCurryアイデアをここで提示する前に自分でテストすることは、私が誤っていない限り、一種のスタックオーバーフローの精神に基づいています。私が伝えたかったのはそれだけでした。ご迷惑をおかけして申し訳ございません。また、それは良い質問だと思い、反対票を投じませんでした。Jochen Ritzelの答えが一番好きです。2番目のステップでの削除の方がはるかに簡単な場合、その場で削除するためにそれらすべてを行う必要があるとは思いません。それは私の見解では好ましい方法であるべきです。
Trilarion

回答:


305

編集:

この答えはPython3では機能せず、RuntimeError

RuntimeError:反復中にディクショナリのサイズが変更されました。

これmydict.keys()は、リストではなくイテレータを返すために発生します。コメントで指摘されているように、単にmydict.keys()リストに変換するだけlist(mydict.keys())で機能します。


コンソールでの簡単なテストでは、繰り返し処理中は辞書を変更できないことがわかります。

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

delnanの回答で述べたように、イテレータが次のエントリに移動しようとすると、エントリを削除すると問題が発生します。代わりに、keys()メソッドを使用してキーのリストを取得し、それを操作します。

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

itemsの値に基づいて削除する必要がある場合は、items()代わりにメソッドを使用します。

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}

53
Python 3では、dict.items()はイテレーターを返します(そしてdict.iteritems()はなくなっています)。
Tim Lesher、2011

83
@TimLesherコメントについて詳しく説明するには...これはPython 3では機能しません
最大

99
@maxのエラボレーションについて詳しく説明するには、上記のコードを2to3で変換すると機能します。デフォルトのフィクサーの1つは、ループをfor k, v in list(mydict.items()):Python 3で正常に動作するように見せることにkeys()なりlist(keys())ます。
ウォルタームント

8
これは機能しません。私はエラーを取得する:RuntimeError: dictionary changed size during iteration
トマーシュZato -復活モニカ

15
Walterが指摘したように@TomášZato、python3の場合はpython3を使用する必要があるためfor k in list(mydict.keys()): 、keys()メソッドをイテレータにし、反復中にdict項目を削除することもできません。list()呼び出しを追加することにより、keys()イテレータをリストに変換します。したがって、forループの本体にいるときは、辞書自体を繰り返し処理する必要はありません。
Geoff Crompton 2017年

89

次の2つの手順で行うこともできます。

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

私のお気に入りのアプローチは通常、新しい口述を作成することです。

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)

11
@senderle:実際には2.7以降。
Jochen Ritzel、2011年

5
dict内包表記アプローチは、辞書のコピーを作成します。幸い、値は少なくとも深くコピーされず、リンクされているだけです。それでもあなたがたくさんの鍵を持っているなら、それは悪いかもしれません。そのため、removeループアプローチの方が好きです。
最大

1
また、ステップを組み合わせることができますfor k in [k for k in mydict if k == val]: del mydict[k]
AXO

最初の解決策は、これまでのところこのスレッドの大きな口述での唯一の効率的な解決策です-完全な長さのコピーを作成しないためです。
kxr 2017

21

反復中にコレクションを変更することはできません。その方法は狂気です-特に、現在のアイテムを削除して削除することが許可されている場合、イテレータは(+1)に移動する必要があり、次のへの呼び出しnextはそれを超えて(+2)するため、 1つの要素(削除した要素のすぐ後ろの要素)をスキップすることになります。次の2つのオプションがあります。

  • すべてのキー(または必要に応じて値、あるいはその両方)をコピーしてから、それらを繰り返します。.keys()これにはet alを使用できます(Python 3では、結果のイテレーターをに渡しますlist)。スペース的には非常に無駄が多いかもしれません。
  • mydict通常どおり繰り返し、削除するキーを別のコレクションに保存しますto_delete。反復が完了したらmydictto_deleteからすべてのアイテムを削除しますmydict。最初のアプローチよりも(削除されたキーの数と滞在数に応じて)スペースをいくらか節約しますが、さらに数行を必要とします。

You can't modify a collection while iterating it.これはdictsや友人のためにちょうど正しいですが、反復中にリストを変更することができますL = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
ニルス・リンデマン

3
@Nils例外はスローされませんが、まだ正しくありません。観察:codepad.org/Yz7rjDVT-説明については、たとえば、stackoverflow.com / q / 6260089/395760を参照してください

ここに来ました。それcan'tshouldn'tリストのためであるべきですが、それでも辞書と友達のためだけに正しいです。
Nils Lindemann、

21

代わりに、によって返されたコピーなどのコピーを反復処理しますitems()

for k, v in list(mydict.items()):

1
これはあまり意味がありません- del v直接することはできないため、決して使用しない各vのコピーを作成しており、いずれにしてもキーで項目にアクセスする必要があります。dict.keys()より良い選択です。
jscs 2011年

2
@ジョシュ:それはすべてv、削除の基準としてどれだけの量を使用する必要があるかに依存します。
Ignacio Vazquez-Abrams

3
Python 3ではdict.items()、コピーではなくイテレータを返します。(悲しいことに)Python 2のセマンティクスも想定しているブレア回答の解説を参照してください。
セシルカレー

11

使用するのが最もきれいです list(mydict)です:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

これは、リストの並列構造に対応しています。

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

どちらもpython2とpython3で動作します。


これは、データセットが大きい場合には適していません。これは、メモリ内のすべてのオブジェクトをコピーしていますよね?
AFP_555

1
@ AFP_555はい-ここでの私の目標は、クリーンで並列なpythonicコードです。メモリ効率が必要な場合、私が知っている最善のアプローチは、削除するキーのリストまたは保存するアイテムの新しい辞書のいずれかを反復して作成することです。Pythonでの私の優先事項は美しさです。大規模なデータセットの場合、GoまたはRustを使用しています。
rsanden

9

辞書内包表記を使用できます。

d = {k:d[k] for k in d if d[k] != val}


これは最もPythonicです。
Yehosef

しかしd、その場で変更するのではなく、新しい辞書を作成します。
アリスティド

9

python3では、dic.keys()で反復すると、辞書サイズエラーが発生します。この代替方法を使用できます。

python3でテストされ、正常に動作し、エラー「反復中に辞書がサイズを変更しました」は発生しません。

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}

4

最初に削除するキーのリストを作成し、次にそのリストを繰り返して削除します。

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]

@Ritzelの最初のソリューションの複製です(完全なコピーなしの大きな辞書で効率的です)。「読まない」w / oリストの理解はありません。それにもかかわらず、それはそれでもおそらくより速いですか?
kxr 2017

3

削除したいアイテムが常にdict反復の「開始」にある場合に適した方法があります

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

「最初」は、特定のPythonバージョン/実装でのみ一貫していることが保証されています。たとえば、Python 3.7の新機能から

dictオブジェクトの挿入順保持の性質は、Python言語仕様の公式の一部であると宣言されています。

この方法により、他の多くの回答が示唆している、少なくともPython 3では、口述のコピーを回避できます。


1

私は上記の解決策をPython3で試しましたが、dictにオブジェクトを格納するときに私に役立つ唯一の解決策のようです。基本的には、dict()のコピーを作成し、元の辞書のエントリを削除しながらそれを繰り返します。

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.