web-dev-qa-db-ja.com

Python列挙による奇妙な動作

ループ内でリストを変更することは想定されていませんが、好奇心から、次の2つの例で反復回数が異なる理由を知りたいと思います。

例1:

_x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    del x[0]
    print(i, s, x)
_

例2:

_x = [1,2,3,4,5]
for i, s in enumerate(x):
    x = [1]
    print(i, s, x)
_

例1は、_i==3_、len(x)==2の場合、3回しか実行されません。

例2は、len(x)==1であっても5回実行されます。

だから私の質問は、enumerateはループの始めに_(index, value)_ペアの完全なリストを生成し、それを反復するのですか?それとも、ループの各反復で生成されますか?

18
dbdq

enumerate() イテレータ、または反復をサポートするその他のオブジェクトを返します。 __ next __()enumerate()によって返されるイテレータのメソッド) は、カウント(開始からデフォルトは0)と反復反復可能から取得した値を含むタプルを返します。

__ next __() コンテナから次のアイテムを返します。それ以上の項目がない場合は、StopIteration例外を発生させます。

Enumerate()は、ループの開始時に(index、value)ペアの完全なリストを生成し、それを反復処理しますか?それとも、ループの各反復で生成されますか?

したがって、enumerate()はイテレータを返し、反復ごとに__next__()はさらに項目があるかどうかをチェックします。 enumerate()は、ループの開始時に完全なリストを作成しません。

@ Wisperwind で述べたように、2番目のケースでは、新しいオブジェクトをxという名前に割り当てています。ループが繰り返されるオブジェクトは、反復中に変更されません。

17
Wasi Ahmad

最初の例では、繰り返し処理しているリストを実際に変更しています。

一方、2番目のケースでは、新しいオブジェクトをxという名前に割り当てるだけです。ただし、ループが繰り返すオブジェクトは変更されません。

Pythonの名前と変数の詳細については、 http://foobarnbaz.com/2012/07/08/understanding-python-variables/ を参照してください。

22
Wisperwind

WasiAhmadとWisperwindが言ったことを明確にしただけです。どちらも「xという名前に新しいオブジェクトを割り当てるだけです」と述べています。これは、「新しいオブジェクトを作成している([1])そしてそれをxという名前で保存します。これに対して、「そうですね、なぜ変更されないのですか?!」何が起こっているかを確認するには、オブジェクトのIDを出力します

x = [1, 2, 3, 4, 5]
y = x  # To keep a reference to the original list
print id(x), id(y)
for i, v in enumerate(x):
    x = [1]
    print id(x), id(y)
print id(x), id(y)


# output (somewhat contrived as I don't have a python environment set up)
#    X ID            Y ID
10000000000001 10000000000001
10000000000002 10000000000001
10000000000003 10000000000001
10000000000004 10000000000001
10000000000005 10000000000001
10000000000006 10000000000001
10000000000006 10000000000001

idxはループを通過するたびに変化し、ループが終了すると、xはで行われた最後の変更を指すことに気付くでしょう。ループ。ループを実行しているときは、参照できるかどうかに関係なく、xの元のインスタンスを繰り返し処理しています。

ご覧のとおり、yは元のxを指しています。ループを繰り返し実行すると、xが変更されていても、yはまだループされている元のxを指しています。

8
FuriousGeorge

実際:最初のスニペットは、反復されたリストを変更します。 2つ目は、変数xを新しいリストにポイントし、enumerate()が横切るリストを変更せずに残します。 www.pythontutor.comの次のリンクにアクセスすると、これが実際に動作していることがわかります。このリンクを使用すると、コードをシングルステップで実行し、変数の内容を視覚化できます。

何が起こっているのかをよりよく理解するには、代わりに here に移動して、次の拡張コードをステップオーバーします。

x = [1,2,3,4,5]
view = enumerate(x)
for i, s in view:
    x = [1]
    print(i, s, x)
1
alexis

2番目の例では、xが指す値のみが変更され、反復しているリストは変更されないことをすでに指摘している人もいます。これは、通常の割り当ての違いの完璧な例です(x = [1])および スライス割り当てx[:] = [1])。後者はリストを変更しますxポイントインプレース

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    x[:] = [1]
    print(i, s, x)

印刷します

(0, 1, [1])
1
Florian Brucker
x = [1, 2, 3, 4, 5]

リスト[1, 2, 3, 4, 5]xで「タグ付け」されています

for i, s in enumerate(x):

enumerate()は別のタグを付加するため、[1, 2, 3, 4, 5]xおよびyのタグが付けられます。 enumerate()は、yタグではなく、xタグを引き続き使用します。

del x[0]

メモリに保存されているリストが変更されたため、xyは両方とも[2, 3, 4, 5]を参照するようになりました。

または、

x = [1]

新しいリスト[1]がメモリに作成され、xタグがそれを指すようになりました。 yタグはまだ元のリストを指しています。

Python変数の仕組み:
http://foobarnbaz.com/2012/07/08/understanding-python-variables/

0
JeremiahBarrar