Pythonの文と式(6): while文を用いたループ

前回はif文での条件分岐と、条件式の作り方を見ました。今回は、if文の仲間であるwhile文を使ったループ構造について紹介します。

目次:Pythonではじめるプログラミング入門

今回の内容

  1. while文とは
  2. break文とcontinue文
  3. ネストされたループ構造
  4. 次のステップ

(1) while文とは

プログラミングにおける「ループ」とは、指定された回数の間だけ同じ処理を繰り返す動作のことを言います。while文はループ構造を作るための文の一種で、一定の条件が満たされる間だけ同じことを繰り返すための文です。Pythonはwhile文を持っていますが、while文そのものはif文と並んでプログラム構造化の基本ともいえる構造です。C言語やそれに連なる言語(Pascal、C++、Javaなど)にはwhile文は必ずありますので、慣れておいて損はないでしょう。

while文を使ってみる

前置きはともかく、while文を使ってみましょう。たとえばコンソールで、以下のコードを書いてみてください。最初の代入文が重要です!

>>> i = 1
>>> while i<=5:
...     print(i)
...     i += 1
...

出力は、下のようになるはずです:

1
2
3
4
5
>>> ■

while文の構成も、if文と似た形になります:

  1. キーワード「while」
  2. 条件式
  3. コロン
  4. インデントされた処理の本体

上の例の場合、条件式は「i<=5」なので、iが5以下である間は「print(i)」と「i+=1」が実行され、「数字iの印字」と「iに1が足される」という処理が行なわれます。より具体的には、以下の順番で処理が行なわれます:

  1. iに1が代入される
  2. while文に入る
    1. i<=5が評価される。1<=5なので、while文本体が実行される
    2. print(i)が実行される
    3. i+=1が実行され、iが2になる
  3. 再びwhile文の最初に戻る
    1. i<=5が再び評価される。2<=5で、本体が実行される
    2. print(i)とi+=1の実行;iは3になる
  4. i=3で、さらにwhile文が実行される; iは4になる
  5. 同上; iは5になる
  6. i=5でwhile文の最初に戻る。
    ここでi<=5がFalseになるので、本体は実行されずにwhile文の処理が終わる

それぞれのループごとにwhile文の最初の条件式が評価され、Trueのときのみwhile文本体が評価される、ということが分かると思います。

while文の取り扱いで注意しなければならないポイントは、初期化処理と更新処理を忘れないということです。以下でもう少し詳しく説明します。

while文と初期化処理

上の例を実行した直後に、あらためて以下の文を入力してみましょう:

>>> while i<=5:
...    print(i)
...    i += 1
...

表示結果はどうなりますか?

>>> ■

もし最初の例を実行した直後に今回の例を実行した場合、何も印字されないはずです。なぜでしょうか。iの値を調べてみましょう:

>>> i
6

実は最初の例を実行すると、while文によってiは6まで足し上がっています。ですので、再度同じwhile文を実行しても最初の「i<=5」がFalseに評価されてしまうため、while文の本体が一度も実行されないのです。つまり、while文が何回実行されるかは、while文で評価される条件の初期値によって変わってきます。言われると当たり前のことですが、while文が思った通りの挙動を示すためには(この例の場合)iの値を適切に指定しておく必要があります。これを「iの初期化(初期化処理)」と呼んでいます。

while文の挙動がiの初期値に依存することを、別の例でも確かめてみましょう。たとえば初期値としてi=3を代入しておくと、どうなるでしょうか?

>>> i = 3
>>> while i<=5: ...     print(i) ...     i += 1 ... 3 4 5 >>> ■

こんどは3から5まで、3回だけループがまわりました。

while文と更新処理

今度は、少し危ない例を出します。予めControlキーと「c」のキーの場所を確認しておいてください(Macをお使いの方へ:スペースの近くにあるCommandキーはControlキーではありません!Macの場合、キーボードで「a」の左にあるのがControlキーです)。

次のコードを入力して、準備ができたら実行してください。今回は、初期化処理も忘れずに書きます:

>>> i = 1
>>> while i<=5:
...     print(i)
...

何が起きましたか?

1
1
1
1
1

たくさんの1が表示されて、いつまでもプロンプト「>>>」が出てこなければ成功です。このままではいつまでたってもプロンプトは出てきませんので、Controlキーを押しながら「c」のキーを押してください

1
1
^C1
1
Traceback (most recent call last):
 File "", line 2, in 
KeyboardInterrupt
>>> ■

多少の差はあるかもしれませんが、だいたい上のような表示になってプロンプトが戻ってくるはずです。コンソールをスクロールさせてみると、非常にたくさんの「1」が印字されていることが分かると思います。いったい何が起こったのでしょうか。

以前の例との違いを見ると分かる通り、今回の例のwhile文には「i+=1」の処理が抜けています。このためiの値はいつまでたっても1のままになってしまい、結果としてwhile文の処理から抜け出せなくなってしまったのです。このような状態を「無限ループ」と表現します。無限ループが発生するのは、主に以下の場合です。

  • while文から抜け出せるような条件を設定しなかった場合
  • 条件に使う変数(ここではi)を適切に更新しなかった場合

極端な話、以下のような文でも無限ループを起こすことができます:

>>> while True:
...     i = 1

この場合、while文の最初で評価する条件式が「True」そのものなので常にTrueとして評価され、いつまでも(意味のない)本体「i=1」が実行され続けることになります(もしこのwhile文を実行してしまったら、先ほどと同じようにControl + cを入力してください;実行後には、iには1が代入されていることでしょう)。

ちなみにControl + cのキーは、「端末割り込みシグナル」を発生させるための制御記号のひとつです(感覚としては\nのような制御文字に近いです)。Pythonの場合、Control+cが入力されることによってすでに実行されているPythonインタプリタの処理が中断し、コンソールの画面に戻ります。大雑把には、「Pythonの実行を中断するための魔法のキー」と考えておいてもよいかもしれません。

Pythonインタプリタが何も処理を行なっていない場合は、”KeyboardInterrupt”という文字が出てくるだけで何も起こりません:

>>> 
KeyboardInterrupt
>>> ■

(2) break文とcontinue文

Pythonを含めwhile文を持つ言語では、ループの中からループ構造を制御するための命令が存在します。それがbreakとcontinueです。

  • break文によって、処理は強制的にループから抜けます。
  • continue文があると、処理は強制的に次のループに飛びます。

break文を使ってみる

break文は、while文の最初の条件式では表せないような複雑な条件を書いたり、柔軟にループを抜けられるようにするためになくてはならない存在です。

たとえばbreak文を使うと、最初の例を次のように書き直すことができます:

>>> i = 1
... while True:
...     print(i)
...     if i == 5:
...         break
...     else:
...         i += 1
...

iが5になるまでは「i+=1」のほうが実行されるので、ループは回り続けます。iが5になったときはbreak文が実行されるので、処理はループを抜けます。今回の例の場合ではコードが複雑になるだけですが、この書き方の魅力は「条件式を、while文本体の処理の後に評価されるようにできる」ということです。例えば以下のように、初期化・更新処理をwhile文の中に入れることができます。

>>> while True:
...     user = input("何か入力: ")
...     if len(user) == 0:
...         print("何か入力してって言ったのに…")
...         break
...     else:
...         print("え、何だって?")
... 

何か入力: こんにちは
え、何だって?
何か入力: 入力したよ
え、何だって?
何か入力: 
何か入力してって言ったのに…
>>> ■

この場合、while文に最初に入ったときにはまだuserは初期化もされていません。userを予め初期化しておこうと思うと最初のinput()をwhile文の前に書かなければなりませんが、それでは何か二度手間なコードになってしまいます。「while True:」と「break」とを組み合わせることで、userの初期化と更新を一箇所にまとめてしまうことができるのです。

よく見ると上の例で作ったプログラムは、まさにコンソールプログラムですね。実はPythonコンソールのような対話型のコンソールプログラムは、このような構造を拡張していくことで作ることができてしまいます。

continue文

continue文は、特定の条件のときにループを「スキップする」のに役立ちます。例えば、最初の例を以下のように書き直してみましょう(じゃっかんコードが冗長ですが、例題ということでお許しください…)。何が起こるか分かりますか?

>>> i = 1
>>> while i <= 5:
...     if i%2 == 0:
...         i += 1
...         continue
...     print(i)
...     i += 1
...

出力は、このようになります:

1
3
5
>>> ■

if文で評価されているのは「i%2 == 0」すなわち「iを2で割った余りが0」ですので、iが偶数(2の倍数)のときには以後のprint関数の方に行かずに次のループに入っています。従って、print関数が実行されるのは1、3、5のときだけということになるのです。

(3) ネストされたループ構造

ループを入れ子にする

breakとcontinueは、現在のループを操作するための構造でした。では、ループが2つ以上あると何が起こるでしょうか?次の例を実行してみましょう(結構長いので、以下の例を直接コンソールにコピー&ペーストしても構いません)。

tail = 0
while tail < 2:
    if tail == 0:
        suffix = "れ"
    else:
        suffix = "の"
    top = 0
    while top < 4:
        if top == 0:
            prefix = "こ"
        elif top == 1:
            prefix = "そ"
        elif top == 2:
            prefix = "あ"
        elif top == 3:
            prefix = "ど"
        print(prefix+suffix)
        top += 1
    print()
    tail += 1

実行すると、以下のような出力が表示されるはずです:

これ
それ
あれ
どれ

この
その
あの
どの

何が起こっているかを説明しますと、このコードは2つのループが入れ子になっており、ループ「while tail < 2」(2~20行目)と、その内部にあるループ「while top < 4」(8~18行目)から成り立っています。この構造を「ループのネスト」あるいは「ネストされたループ」と呼びます。「ネスト」は「入れ子」という程度の意味で、プログラミングの世界では入れ子状の構造をネストと(日本語でも)呼ぶことがあります。

外側のループではtailを用いてループと変数suffixを制御していて、tailの値に応じてsuffixに「れ」あるいは「の」が代入されます(3~6行目)。これに対して内側のループでは、topの値に応じて変数prefixに「こ」、「そ」、「あ」、「ど」が代入されます(9~16行目)。

そして内側のループにおいて、各ループの最後に「print(prefix+suffix)」が実行されます(17行目)。文字列オブジェクト同士の足し算は「文字列をつなげる」という効果になりますので、「これ」「それ」…という単語が印字されるのです。内側のループ用変数topは外側のループごとに初期化されますので(7行目)、最初のループでは「これ」「それ」…が、次のループでは「この」「その」…が、それぞれ印字されます。

さらに内側のループが一通り終わった後、外側のループでは「print()」が実行されます。引数無しでprint関数を実行していますが、これは結果としては「print(“”)」と同じで空の改行だけが出力されます。ですので、「これ」「それ」…と「この」「その」…の後に空行が1行ずつあるのです。

このように、ループをネスト(入れ子に)することで、複雑な繰り返し処理を組み立てていくことができます。たとえば「ハードディスクの中にある全てのファイル名を調べてリストにする」「特定のWebページのリンクからnステップ以内で行けるページ全てをリストアップする」といった処理も、原理的にはループのネストを使って実現することができます(実際にするかどうかはともかくとして)。

ネストされたループでのbreakとcontinue

さて、先ほどの例の中にbreakやcontinueを入れてみましょう。4行目と5行目の間(suffix = “れ”の次の行)にbreakあるいはcontinueを入れるとどのような出力になるでしょうか?

tail = 0
while tail < 2:
    if tail == 0:
        suffix = "れ"
        break
    else:
        suffix = "の"
    top = 0
    while top < 4:
        if top == 0:
            prefix = "こ"
        elif top == 1:
            prefix = "そ"
        elif top == 2:
            prefix = "あ"
        elif top == 3:
            prefix = "ど"
        print(prefix+suffix)
        top += 1
    print()
    tail += 1
tail = 0
while tail < 2:
    if tail == 0:
        suffix = "れ"
        continue
    else:
        suffix = "の"
    top = 0
    while top < 4:
        if top == 0:
            prefix = "こ"
        elif top == 1:
            prefix = "そ"
        elif top == 2:
            prefix = "あ"
        elif top == 3:
            prefix = "ど"
        print(prefix+suffix)
        top += 1
    print()
    tail += 1

インデントを前の行と揃えることに注意してください。そうしないとSyntaxErrorが出ます。

正解は…

  • breakを入れた場合:何も出力されません(最初のprintの前にbreakしてしまう)
  • continueを入れた場合:無限ループになります(tailが更新されないまま、ループがまわり続ける)

continueの例は無粋なので、continue文の直前にさらに更新「tail+=1」を入れてみましょう。

tail = 0
while tail < 2:
    if tail == 0:
        suffix = "れ"
        tail += 1
        continue
    else:
        suffix = "の"
    top = 0
    while top < 4:
        if top == 0:
            prefix = "こ"
        elif top == 1:
            prefix = "そ"
        elif top == 2:
            prefix = "あ"
        elif top == 3:
            prefix = "ど"
        print(prefix+suffix)
        top += 1
    print()
    tail += 1
  • tailを更新してcontinueにした場合:「この」「その」…だけが表示され、「これ」「それ」…は表示されません(「れ」のときのprintはスキップされる)

上の3種類の挙動は、もともとここまでの例でやってきた通りです。

さて今度は、breakとcontinueを内側のループに入れてみましょう。具体的には、12行目「prefix=”そ”」の後に、以下の文を挿入してみましょう。先ほどの外側のループに加えた変更は、もとに戻してください:

  1. tail = 0
    while tail < 2:
        if tail == 0:
            suffix = "れ"
        else:
            suffix = "の"
        top = 0
        while top < 4:
            if top == 0:
                prefix = "こ"
            elif top == 1:
                prefix = "そ"
                break
            elif top == 2:
                prefix = "あ"
            elif top == 3:
                prefix = "ど"
            print(prefix+suffix)
            top += 1
        print()
        tail += 1
    
    
  2. tail = 0
    while tail < 2:
        if tail == 0:
            suffix = "れ"
        else:
            suffix = "の"
        top = 0
        while top < 4:
            if top == 0:
                prefix = "こ"
            elif top == 1:
                prefix = "そ"
                top += 1
                continue
            elif top == 2:
                prefix = "あ"
            elif top == 3:
                prefix = "ど"
            print(prefix+suffix)
            top += 1
        print()
        tail += 1
    
    

それぞれ実行してみてください。

結果は以下のようになるはずです:

  1. break文を挿入した場合:「これ」「この」のみ表示されます
  2. continue文(+代入文)を挿入した場合:「それ」「その」以外が表示されます

なぜこのようになるかというと、break文やcontinue文は、直近(一番内側)のループのみに作用するからです。たとえば「それ」のときにbreakが実行されると、処理は内側の「while top<4」のループから抜けます。このため「これ」だけが表示され、「それ」「あれ」「どれ」の表示はスキップされます。ところがPythonの処理は外側の「while tail<2」のループには留まりますので、print()の実行の後ループがまわって「suffix=”の”」の場合が実行されるのです。結果として「これ」(空行)「この」(空行)という出力になるのです。

continue文の場合も同様で、「それ」あるいは「その」のときだけ内側のループのみがスキップされますので、それ以外の「これ」「あれ」「どれ」と、「この」「あの」「どの」とが出力されるのです。

(4) 次のステップ

今回はwhile文を中心に、関連するbreak文とcontinue文を含めて紹介しました。軽く見てきたようにwhileとifを組み合わせることで、簡単なコンソールプログラムや連続繰り返し処理を書くことができるようになります。

その一方で一回に書くコードの量が多くなってきて、コンソール上ではとても書きにくくなってきましたね。そこで次回からは、外部のファイルからPythonコードを実行する方法を紹介していきたいと思います。

続き:Pythonファイルの実行(1): Pythonスクリプトを作る

目次:Pythonではじめるプログラミング入門

投稿者: gwappa

A free-floating DIY programmer who loves to automate chores in a royalty-free manner. Mainly uses Python, Java and C++.

“Pythonの文と式(6): while文を用いたループ” への 3 件のフィードバック

コメントを残す