web-dev-qa-db-ja.com

forループのインデックス変数としてリストインデックスを使用できるのはなぜですか?

私は次のコードを持っています:

a = [0,1,2,3]

for a[-1] in a:
  print(a[-1])

出力は次のとおりです。

0
1
2
2

リストインデックスをforループのインデックス変数として使用できる理由について私は混乱しています。

95
Kundan Verma

式_a[-1]_の_for a[-1] in a_などのリストインデックスは、 _for_stmt_ (具体的には _target_list_ =)文法トークン。slicingは割り当ての有効なターゲットです。

「ハァッ?割り当て?thatは私の出力と何の関係があるの?」

確かに、出力と結果に関係するすべてがあります。 _for-in_ループのドキュメント に飛び込みましょう。

_for_stmt ::=  "for" target_list "in" expression_list ":" suite
_

式リストは1回評価されます。反復可能なオブジェクトを生成する必要があります。イテレータは_expression_list_の結果に対して作成されます。その後、スイートは、イテレータによって返される順序で、イテレータによって提供される各アイテムに対して1回実行されます。 各アイテムは、割り当ての標準ルールを使用してターゲットリストに割り当てられます割り当てステートメント)を参照 )、スイートが実行されます。

(強調を追加)
N.B。 suiteはforブロックの下のステートメントを参照します。特定のケースではprint(a[-1])です。

少し楽しくして、printステートメントを拡張しましょう。

_a = [0, 1, 2, 3]
for a[-1] in a:
    print(a, a[-1])
_

これにより、次の出力が得られます。

_[0, 1, 2, 0] 0    # a[-1] assigned 0
[0, 1, 2, 1] 1    # a[-1] assigned 1
[0, 1, 2, 2] 2    # a[-1] assigned 2
[0, 1, 2, 2] 2    # a[-1] assigned 2 (itself)
_

(コメントの追加)

ここでは、_a[-1]_が各反復で変更され、この変更がaに伝播されることがわかります。繰り返しますが、これはslicingが有効なターゲットであるために可能です。

Ev。Kounis による適切な引数は、上記の引用されたドキュメントの最初の文を考慮します: "式リストは1回評価されます"。これは、式リストが静的で不変であり、_[0, 1, 2, 3]_で一定であることを意味しませんか?したがって、_a[-1]_は、最後の繰り返しで_3_に割り当てられるべきではありませんか?

さて、 Konrad Rudolph は次のように主張しています:

いいえ、[式リストは] 反復可能なオブジェクトを作成するために一度評価されます。しかし、その反復可能なオブジェクトは、そのコピーではなく元のデータを繰り返し処理します。

(強調を追加)

次のコードは、反復可能なitlazilyがもたらす リストの要素x

_x = [1, 2, 3, 4]
it = iter(x)
print(next(it))    # 1
print(next(it))    # 2
print(next(it))    # 3
x[-1] = 0
print(next(it))    # 0
_

Kounis 'に触発されたコード)

評価が熱心であれば、_x[-1] = 0_がitにゼロの影響を与え、_4_が出力されることを期待できます。これは明らかにnotであり、同じ原理でfor-loopであることを示します各反復で_a[-1]_への割り当てに続いて、aから遅延して数値を生成します。

97
TrebledJ

(これは答えというより長いコメントです-特に良いものがいくつかあります-特に( @ TrebledJ's )。クリックしてくれました。)

あなたが持っていた場合

x = 0
l = [1, 2, 3]
for x in l:
    print(x)

xがループを通過するたびにオーバーライドされることは驚くことではありません。 xは以前に存在していましたが、その値は使用されません(つまり、for 0 in l:、これはエラーをスローします)。むしろ、lからxに値を割り当てます。

するとき

a = [0, 1, 2, 3]

for a[-1] in a:
  print(a[-1])

たとえ a[-1]はすでに存在し、値を持っています。その値は入れず、a[-1]ループを通るたびに。

24
Nathan

それは興味深い質問であり、それによってあなたはそれを理解することができます:

for v in a:
    a[-1] = v
    print(a[-1])

print(a)

実際にはaは次のようになります:[0, 1, 2, 2]ループの後

出力:

0
1
2
2
[0, 1, 2, 2]

これがお役に立てば幸いです。さらに質問があればコメントしてください。 :)

11
recnac

forループステートメントの左側の式は、各反復で右側の反復可能オブジェクトの各アイテムに割り当てられるため、

for n in a:
    print(n)

単におしゃれな方法です:

for i in range(len(a)):
    n = a[i]
    print(n)

同様に、

for a[-1] in a:
  print(a[-1])

単におしゃれな方法です:

for i in range(len(a)):
    a[-1] = a[i]
    print(a[-1])

ここで、各反復では、aの最後のアイテムがaの次のアイテムに割り当てられます。したがって、反復が最後のアイテムに到達すると、その値は最後から2番目に割り当てられますアイテム、2

11
blhsing

答えはTrebledJ は、これが可能な理由の技術的理由を説明しています。

なぜあなたはこれをしたいのですか?

配列を操作するアルゴリズムがあるとします:

_x = np.arange(5)
_

そして、最初のインデックスの異なる値を使用してアルゴリズムの結果をテストしたいと思います。最初の値をスキップするだけで、毎回配列を再構築できます。

_for i in range(5):
    print(np.r_[i, x[1:]].sum())
_

_np.r__

これは、反復ごとに新しい配列を作成しますが、特に配列が大きい場合は理想的ではありません。すべての反復で同じメモリを再利用するには、次のように書き換えます。

_for i in range(5):
    x[0] = i
    print(x.sum())
_

これはおそらく最初のバージョンよりも明確です。

しかし、これはこれを記述するよりコンパクトな方法とまったく同じです。

_for x[0] in range(5):
    print(x.sum())
_

上記のすべての結果:

_10
11
12
13
14
_

さて、これは些細な「アルゴリズム」ですが、配列内の単一(または複数ですが、割り当てのアンパックによるもの)値を、多くの値に変更することをテストするより複雑な目的があります。アレイ全体をコピーします。この場合、ループでインデックス付きの値を使用することもできますが、コードを管理している人(自分を含む)を混乱させる準備をしてください。このため、_x[0] = i_を明示的に割り当てる2番目のバージョンが望ましいでしょうが、よりコンパクトなfor x[0] in range(5)スタイルを好む場合、これは有効なユースケースでなければなりません。

9
gerrit