web-dev-qa-db-ja.com

辞書の無限ループが予期せず終了します

Python(通常のwhile True以外))で無限ループを作成するさまざまな方法を試して、このアイデアを思い付きました:

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None  # Value doesn't matter, so I set it to None
    print(i)

紙の上では、これが無限にループする方法を追跡しました。

  1. 辞書のキーの値をループします
  2. そのエントリを削除します。
  3. ループ内の現在のカウンター位置+ 1は、辞書を更新する値Noneを持つ新しいキーになります。
  4. 現在のカウンターを出力します。

これは、私の頭の中では、一種の無限ループ形式で自然数を出力するはずです:

0
1
2
3
4
5
.
.
.

このアイデアは賢いと思いましたが、Python 3.6で実行すると、次のように出力されます。

0
1
2
3
4

はい、5回繰り返した後、何とか停止しました。明らかに、ループのコードブロックには基本条件またはセンチネル値がないため、Pythonがこのコードを5回しか実行しないのはなぜですか?

42
Suraj Kothari

ループで変更した場合、すべてのdictエントリを反復処理する保証はありません。 docs から:

辞書のエントリを追加または削除しながらビューを反復すると、RuntimeErrorが発生するか、すべてのエントリを反復できない可能性があります。

itertools.count() を使用して、最初の試行と同様の「列挙型」無限ループを作成できます。例えば:

from itertools import count

for i in count():
    print(i)
    # don't run this without some mechanism to break the loop, i.e.
    # if i == 10:
    #     break

# OUTPUT
# 0
# 1
# 2
# ...and so on
44
benvc

この場合、@ benvcが書いたように、これが機能することは保証されていません。しかし、なぜそれがC-Pythonで機能するのか疑問に思われる場合のために:

C-Python実装は、いくつかの挿入後にdictオブジェクトを破棄し、メモリ内の新しいスペースにコピーします。削除は気にしません。そのため、これが発生すると、ループがそれに気づき、例外で中断します。

これについての詳細、およびその他の興味深いpython内部については、こちらをご覧ください。

https://github.com/satwikkansal/wtfpython#-modifying-a-dictionary-while-iterating-over-it

8
HWM-Rocker

私はあなたのコードをpython2とpython3でテストしました

python3 output
0,1,2,3,4
python2
0,1,2,3,4,5,6,7

起こっている可能性のあることが1つ思い浮かびます。最初のキー値を作成するときにディクショナリに割り当てられているメモリ量は一定であり、キー値を削除するときにメモリを割り当てたり割り当てを解除したりせず、値を削除するだけです。割り当てられたメモリがすべて使用されると、終了します。そのデルなしで実行すると、このエラーが発生するため

RuntimeError: dictionary changed size during iteration

したがって、pythonは、そのキーの値といくつかに十分なメモリを作成します。それが使用されると、ディクショナリに割り当てられたメモリはなくなります。

5
user8128927

多くの人が指摘したように、forループを使用して反復中にデータ構造を変更することはお勧めできません。ただし、whileループを使用すると、各反復でループ条件を再評価するため、これを行うことができます(まだ、代替案として提案されている人はいません)。正しいループ条件を見つける必要があります。スクリプトは次のようになる必要があります。

_x = {0: None}
while x:
    i, _ = x.popitem()
    print(i)
    # to avoid infinite loop while testing
    # if i == 10:
    #     break
    x[i+1] = None
_

Pythonでは、ディクショナリは空のときは不正なものになります( docs を参照)。ループは、反復の開始時にxが空の場合にのみ停止します。ディクショナリにはキーと値のペアが1つしかないため、そのペアを取得してディクショナリから削除するには popitem() で十分です。辞書が空になった直後に次の整数が追加されるため、評価時にループ条件が偽になることはなく、無限ループになります。

3
J.Baoby