結局 for i in range() ってなに?

はじめに

こんにちは、高校部長です。
python歴だいたい3ヶ月の初心者です。

さて、2023の3月に今年度から新課程で新しく加わった情報Iの学期末テストを受けたのです。

そこで、以下のようなコードが:

for i in range(0, 6, 3):
    i = i + 3
    print(i)

僕はその時単純に

「なるほどな!じゃあこれをC++っぽく直すと、」

for(int i = 0; i < 6; i += 3){
    i += 3;
    std::cout << i << std::endl;
}

「になるから…2回目のループの初めではi6が代入されているから実行されず、出力結果は3だけに違いない!!」

と考えたのですよ。そしてなんと答えは

3
6

????????????

range

range はPythonの組み込み型の一種らしい。

初期化は

range(stop)
range(start, stop[, step])

公式ドキュメント^1曰く、

step が正の場合、range r の内容は式 r[i] = start + step*i で決定されます。ここで、 i >= 0 かつ r[i] < stop です。

どうやらrangelistのような挙動するらしい。試しにlist型にキャストしてみると、

list(range(10))  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

list(range(0, 6, 3))  # [0, 3]

つまりfor i in range(0, 6, 3)というのはfor i in [0,3]と同じ挙動をするということだ。

for i in [0, 3]:
  i = i + 3
  print(i)
#3
#6

つまるところ処理の終わりでカウンタ変数iに3がたされて、iが0、もしくは3であった場合は処理を実行するということ?だと考えていたのだが、どうやらそうではないらしい…むしろそのように書きたいのならwhile文で書くべきだし……

for ってなんだろう

公式ドキュメントからの引用^2:

for_stmt ::=  "for" target_list "in" starred_list ":" suite
              ["else" ":" suite]

The starred_list expression is evaluated once;
An iterator is created for that iterable.
The first item provided by the iterator is then assigned to the target list
for ループはターゲットリスト内の変数への代入を行います。 これにより、for ループ内も含めて、それ以前の全ての代入は上書きされます:

for i in range(10):
    print(i)
    i = 5             # this will not affect the for-loop
                      # because i will be overwritten with the next
                      # index in the range

つまり、まずrange(0, 6, 3)forループが始まるときに1度だけ評価されて、そのあとはイテレータである[0, 3]が生成され、最初のiにその最初の要素が代入されるという。

そしてsuiteの終わりにはiに加算されるのではなく、次のイテレータの要素が代入される。つまり、

for i in range(0, 6, 3):
    i = i + 3
    print(i)

においては、一回目のループでi0が代入されて3が足されてそれを表示し、二回目のループでforによってi3が代入されてそれに3が足されて6が表示されるということ。

なるほどなー。

まとめ

for文はカウンタ変数にinの後に記述したイテラブルなオブジェクトの要素を一つずつ代入する。カウンタ変数はループ内で値を代入しても、ループの始めでコレクションの次の要素が代入される。だからrangeの代わりにlist型を入れてそれそれの要素を取り出すという芸当が可能になるのですね。

じゃあそれforeachじゃねぇか!!!^3

さいごに

ラムダ式楽しい!!!

  • X