前章では、プログラムに特定のブロックを実行させて別のブロックを飛ばさせる方法を説明しました。フロー制御はこれだけではありません。本章では、ループを使ってブロックを繰り返し実行する方法を説明します。Pythonには、whileとforの2種類のループがあります。これらを使うと1秒で何百万回もコードを実行できるので、自動化の力を最大限に発揮できます。モジュールと呼ばれるコードライブラリをインポートする方法も説明します。これにより利用できる関数を増やすことができます。
while文を使うとコードを何度も繰り返し実行できます。while文の条件がTrueである限り、while節のコードが実行されます。while文は常に次のような形になります。
while文はif文と似ていますが、動作が違います。if節が終わるとプログラムの実行はそのあとから継続します。while節が終わるとプログラムの実行はwhile文の先頭に戻ります。while節はwhileループ(あるいは単にループ)と呼ばれることが多いです。
条件が同じで実行するプログラムも同じであるif文とwhileループの動作を比べてみましょう。こちらがif文のコードです。
spam = 0
if spam < 5:
print('Hello, world.')
spam = spam + 1
こちらがwhile文のコードです。
spam = 0
while spam < 5:
print('Hello, world.')
spam = spam + 1
この2つのコードはよく似ています。if文もwhile文もspamの値を確認し、5よりも小さければメッセージを表示します。しかしこの2つのコードを実行すると、大きな違いがあります。if文では"Hello, world."が一度だけ表示されます。while文では"Hello, world."が5回繰り返し表示されます。図3-1と図3-2のフローチャートを見比べてください。
図 3-1:if文のコードのフローチャート
if文のコードは条件を確認し、その条件が真であればHello, world.を一度だけ表示します。whileループのコードは5回表示します。spamに格納される整数はループごとに1つずつ増えていきますから、5回繰り返したあとにはspam < 5がFalseになります。
図 3-2:while文のコードのフローチャート
whileループでは条件が反復(イテレーション)ごとに判定されます。つまり、ループが実行されるごとに条件が判定されます。条件がTrueであれば節が実行され、そのあとで条件が再び判定されます。条件がFalseになると、while節が飛ばされます。
これは文字通りにyour name(あなたの名前)の入力を求め続けるサンプルプログラムです。Muエディタの画面上部の ボタンを押し、以下のコードを入力して、yourName.pyという名前で保存してください。
name = ''
while name != 'your name':
print('Please type your name.')
name = input('>')
print('Thank you!')
このプログラムでは、まず、変数nameに空文字列を設定します。name != 'your name'の条件はTrueですから、whileループ節を実行します。
while節ではユーザーに名前の入力を求め、入力された値を変数nameに代入します。while節の最終行に達したので、ループの最初に戻り、条件を再評価します。nameの値が'your name'でなければ、条件はTrueになり、while節がもう一度実行されます。
ユーザーがyour nameと入力すると、whileループの条件が'your name' != 'your name'となり、Falseに評価されます。whileループ節を実行せず、残りのプログラムに進みます。図3-3はyourName.pyプログラムのフローチャートです。
図 3-3: yourName.py プログラムのフローチャート
yourName.pyを動かしてみましょう。F5キーを押すと実行できます。your name以外の内容を何度か入力してから、最後にyour nameと入力してみてください。
Please type your name.
>Al
Please type your name.
>Albert
Please type your name.
>%#@#%*(^&!!!
Please type your name.
>your name
Thank you!
your nameと入力しない限り、whileループの条件はFalseにならないので、永遠に入力を求められます。このプログラムでは、input()関数の呼び出しに対して正しい文字列(your name)を入力すると、ループから抜け出せます。条件が常にTrueとなるループを作ってしまうことがあります。次はwhileループから抜け出す方法を見てみましょう。
whileループ節から抜け出す方法があります。break文を実行すると、whileループ節から抜けます。break文はbreakというキーワードだけから成り立ちます。
break文を使って先ほどの yourName.pyプログラムと同じ動作をするプログラムです。次のコードをyourName2.pyという名前で保存してください。
while True:
print('Please type your name.')
name = input('>')
if name == 'your name':
break
print('Thank you!')
1行目で無限ループを作っています。条件が常にTrueになるwhile ループです。(Trueは常にTrueに評価されます。)このループから抜け出すには、break文の実行が必要です。(抜け道のない無限ループというバグを作ってしまうことがよくあります。)
先のプログラムと同様に、ユーザーにyour nameの入力を求めます。今回はwhileループの中でif文を使い、nameが'your name'に等しいかどうかを確認しています。その条件がTrueになれば、break文を実行します。そして、ループから出て、print('Thank you!') を実行します。それ以外の場合は、break文を含むif節は飛ばされ、whileループの最後に到達します。ここまで来たら、while文の最初に戻り、条件を再確認します。whileループの条件はTrueというブール値ですから、ループの中に入り、もう一度your nameの入力をユーザーに求めます。図3-4はこのプログラムのフローチャートです。
図 3-4:無限ループのyourName2.pyプログラムのフローチャート。ループの条件が常にTrueですから、X印の経路をたどることは絶対にありません。
yourName2.pyを実行して、yourName.pyを実行したときと同じように試してみてください。どちらも同じように動作するはずです。
break文と同じように、continue文はループの中で使います。continue文を実行すると、ループの最初に戻り、ループの条件を再評価します。(ループの最後まで到達したときの動作と同じです。)
continueを使い、名前とパスワードを尋ねるプログラムを書いてみましょう。以下のコードをswordfish.pyという名前で保存してください。
while True:
print('Who are you?')
name = input('>')
❶ if name != 'Joe':
❷ continue
print('Hello, Joe. What is the password? (It is a fish.)')
❸ password = input('>')
if password == 'swordfish':
❹ break
❺ print('Access granted.')
ユーザーがJoe以外の名前を入力すると(➊)、continue文(➋)によりループの最初に戻ります。条件を再評価し、値がTrueなので、ループの中に入ります。(ユーザーがJoeという名前を入力して)if文を突破すると、パスワードの入力が求められます(➌)。swordfishというパスワードが入力されると、break文(➍)が実行され、whileループを抜けて、Access grantedと表示されます(➎)。それ以外のパスワードが入力されると、whileループの最後に到達し、ループの最初に戻ります。図3-5<はこのプログラムのフローチャートです。
図 3-5:swordfish.py プログラムのフローチャート。ループの条件は常にTrueですから、X印の経路をたどることは絶対にありません。
このプログラムを実行して、入力を試してみてください。Joeと入力するまで、プログラムはパスワードの入力を求めることはありません。正しいパスワードを入力すると、プログラムは終了します。
Who are you?
>I'm fine, thanks. Who are you?
Who are you?
>Joe
Hello, Joe. What is the password? (It is a fish.)
>Mary
Who are you?
>Joe
Hello, Joe. What is the password? (It is a fish.)
>swordfish
Access granted.
whileループでは条件がTrueである間はループの実行を繰り返しますが、コードブロックを一定回数実行したいときにはどうすればよいでしょうか。forループ文とrange()関数を使います。
for文はfor i in range(5):のような形で使います。
forループの動作を確認するために新しいプログラムを書いて、fiveTimes.pyという新しい名前で保存します。
print('Hello!')
for i in range(5):
print('On this iteration, i is set to ' + str(i))
print('Goodbye!')
forループの節が5回実行されます。1回目の実行時には、変数iに0が設定されます。ループ節でprint()関数を呼び出し、On this iteration, i is set to 0を表示します。forループ節の実行を終えるとループの最初に戻り、for文のiを1増加させます。このようにしてrange(5)が節の実行を5回繰り返します。iが0、1、2、3、4と増えていきます。変数iは、range()関数に渡された整数まで増加していきます(その整数は含みません)。図3-6はfiveTimes.pyプログラムのフローチャートです。
図 3-6:fiveTimes.pyプログラムのフローチャート
プログラムの出力はこのようになります。
Hello!
On this iteration, i is set to 0
On this iteration, i is set to 1
On this iteration, i is set to 2
On this iteration, i is set to 3
On this iteration, i is set to 4
Goodbye!
forループでもbreak文とcontinue文を使うことができます。continue文はforループのカウンターを次の数に進めて実行を続けます。ループの最後に到達してループの最初に戻るのと同じ要領です。continue文とbreak文を使えるのは、whileループとforループの中だけです。それ以外の場所で使おうとしたら、エラーが発生します。
forループの別の例を見てみましょう。数学者ガウスの逸話をもとにしています。ガウスは少年時代に先生から手間のかかる問題を出されました。0から100までを全部足すという問題です。ガウスは賢い方法を思いついて数秒で答えを出しましたが、forループを使って(愚直に)その計算をするプログラムを書けます。
total = 0
for num in range(101):
total = total + num
print(total)
答えは5,050になるはずです。プログラムの実行開始時に変数totalの値を0に設定します。forループで、total = total + numを101回実行します。毎回変数numの値が1ずつ増えていきます。0から100まで101回繰り返してtotalで合計を得ます。totalを画面に表示します。非常に遅いコンピュータでも、これくらいのプログラムでしたら1秒もかからないでしょう。
(ガウス少年は数秒でこの問題を解く方法を見つけました。1 + 100, 2 + 99, 3 + 98…50 + 51と、合計すると101になるペアが50組あります。50 × 101は5,050ですから、0から100までの合計は5,050です。賢い子どもです。)
forループと同じことをwhileループでもできますが、forループのほうが簡潔です。forのfiveTimes.pyをwhileループで書き直してみましょう。
print('Hello!')
i = 0
while i < 5:
print('On this iteration, i is set to ' + str(i))
i = i + 1
print('Goodbye!')
プログラムを実行したときの出力はforループを使ったfiveTimes.pyと同じです。forループは回数を指定してループさせるときに便利で、whileループは特定の条件が満たされる限りループさせるときに便利です。
カンマ区切りで複数の引数を取れる関数があります。range()関数がそうです。range()関数に引数を渡すことで、ゼロ以外の数字から連続する整数を得ることができます。
for i in range(12, 16):
print(i)
第一引数はforループの変数が開始する数値であり、第二引数は終了する数値(その数値は含まない)です。
12
13
14
15
range()関数に3つの引数を渡すこともできます。第一引数と第二引数は開始と終了で、第三引数は増分です。増分とは、繰り返しごとに変数の値が増やされる量のことです。
for i in range(0, 10, 2):
print(i)
range(0, 10, 2)は、0から8まで2ずつ増やしていきます。
0
2
4
6
8
range()関数はforループで柔軟に使えます。例えば、増分に負の数を指定すると、forループでカウントダウンをできます。
for i in range(5, -1, -1):
print(i)
このforループの出力は次のとおりです。
5
4
3
2
1
0
range(5, -1, -1)のforループでiを表示すると、5から0までカウントダウンします。
Pythonでは、これまで見てきたように、print()、input()、len()などの組み込み関数をどこからでも呼び出せます。組み込み関数とは別に、Pythonには標準ライブラリと呼ばれるモジュールがあります。各モジュールは、プログラムに組み込むことができる一連の関数を集めたものです。例えば、mathモジュールは数学に関連する関数、randomモジュールはランダムな数値に関連する関数、といった具合です。
これらの関数を使う前に、import文でモジュールをインポートしなければなりません。import文は次のように書きます。
モジュールをインポートすると、そのモジュールに含まれるすべての関数を使えるようになります。randomモジュールを試してみましょう。random.randint()関数が使えるようになります。
次のコードをprintRandom.pyという名前で保存してください。
import random
for i in range(5):
print(random.randint(1, 10))
このプログラムを実行すると、次のように出力されます。
4
1
8
4
1
random.randint()関数を呼び出すと、渡した2つの整数の間でランダムな整数値に評価されます。randint()はrandomモジュール内にありますから、関数名の前にrandom.とつけなければなりません。そうすればPythonがrandomモジュール内の関数を探してくれます。
4つのモジュールをインポートするimport文の例です。
import random, sys, os, math
これで4つのモジュールの中にある関数が使えるようになります。これらの関数については本書のあとの部分で詳しく学びます。
importの前にfrom というキーワードが来る文もあります。from random import *のように、fromのあとにモジュール名を書き、importのあとにキーワードやアスタリスク(*)などを書きます。この形でimport文を書くと、randomモジュール中の関数を呼び出すときにrandom.をつける必要がなくなります。とはいえ、モジュール名をつけたほうがわかりやすいですから、import randomの形の文を書いたほうがよいです。
フロー制御の説明の締めくくりとしてプログラムを途中で終了する方法をお伝えします。命令の最後に到達したらプログラムの実行は終了します。しかし、sys.exit()関数を呼び出せば、命令の最後に到達する前にプログラムを終了させられます。この関数はsysモジュール内にありますから、使用前にsysをインポートしなければなりません。
次のコードをexitExample.pyという名前で保存してください。
import sys
while True:
print('Type exit to exit.')
response = input('>')
if response == 'exit':
sys.exit()
print('You typed ' + response + '.')
このプログラムを実行してみてください。break文のない無限ループです。このプログラムを終了させるために、sys.exit()を呼び出します。変数responseの値が'exit'と等しければ、sys.exit()を呼び出す行が実行されます。変数responseはinput()関数により設定されますから、ユーザーがexitと入力すればプログラムを終了させられます。
ここまでは例を通じて基本概念を説明してきました。ここからは学んだことを組み合わせてもっと本格的なプログラムを書いてみましょう。この節では、単純な数当てゲームを紹介します。このプログラムを実行すると、次のように出力されます。
I am thinking of a number between 1 and 20.
Take a guess.
>10
Your guess is too low.
Take a guess.
>15
Your guess is too low.
Take a guess.
>17
Your guess is too high.
Take a guess.
>16
Good job! You got it in 4 guesses!
次のコードをguessTheNumber.pyという名前で保存してください。
# 数当てゲーム
import random
secret_number = random.randint(1, 20)
print('I am thinking of a number between 1 and 20.')
# プレイヤーは6回推測する
for guesses_taken in range(1, 7):
print('Take a guess.')
guess = int(input('>'))
if guess < secret_number:
print('Your guess is too low.')
elif guess > secret_number:
print('Your guess is too high.')
else:
break # 推測した数が当たっている場合
if guess == secret_number:
print('Good job!You got it in ' + str(guesses_taken) + ' guesses!')
else:
print('The number was ' + str(secret_number))
このコードを最初から一行ずつ見ていきましょう。
# 数当てゲーム
import random
secret_number = random.randint(1, 20)
1行目はプログラムの動作を説明するコメントです。2行目でrandomモジュールをインポートしています。あとでユーザーに当ててもらう数を生成するのにrandom.randint()関数を使うためです。その関数の返り値である1から20までのランダムな整数を、変数secret_numberに格納します。
print('I am thinking of a number between 1 and 20.')
# プレイヤーは6回推測する
for guesses_taken in range(1, 7):
print('Take a guess.')
guess = int(input('>'))
秘密の数があって、それを6回以内に当ててもらいます。プレイヤーが推測した数を入力し、最大6回繰り返すforループの中で当たっているかどうか確かめます。ループの中ではまずプレイヤーが推測した数を入力します。input()関数は文字列を返しますから、int()関数で文字列を整数値に変換します。それをguessという名前の変数に格納します。
if guess < secret_number:
print('Your guess is too low.')
elif guess > secret_number:
print('Your guess is too high.')
この4行では、プレイヤーが推測した数が秘密の数よりも小さいか大きいかを確認しています。どちらの場合も、推測した数が小さすぎるか大きすぎるかのヒントが画面に表示されます。
else:
break # 推測した数が当たっている場合
推測した数が大きすぎも小さすぎもしない場合、つまり秘密の数に等しい場合、forループから抜け出します。
if guess == secret_number:
print('Good job!You got it in ' + str(guesses_taken) + ' guesses!')
else:
print('The number was ' + str(secret_number))
forループのあとでは、if-else文でプレイヤーが数を当てられたかどうかを確認し、その結果に応じたメッセージを画面に表示しています。どちらの場合も、推測に挑戦した回数のguesses_takenと秘密の数のsecret_numberの値を表示します。その整数値を文字列と結合するので、整数値をstr()関数に渡し、整数値から文字列値を得ています。それらの文字列を+演算子で結合し、最後にprint()関数に渡しています。
これまでに学んだ概念を使って、簡単なじゃんけんプログラムを作ってみましょう。出力は次のようになります。
ROCK, PAPER, SCISSORS
0 Wins, 0 Losses, 0 Ties
Enter your move: (r)ock (p)aper (s)cissors or (q)uit
>p
PAPER versus...
PAPER
It is a tie!
0 Wins, 0 Losses, 1 Ties
Enter your move: (r)ock (p)aper (s)cissors or (q)uit
>s
SCISSORS versus...
PAPER
You win!
1 Wins, 0 Losses, 1 Ties
Enter your move: (r)ock (p)aper (s)cissors or (q)uit
>q
次のコードをrpsGame.pyという名前で保存してください。
import random, sys
print('ROCK, PAPER, SCISSORS')
# プレイヤーの勝ち数、負け数、引き分け数を格納する3つの変数の初期化
wins = 0
losses = 0
ties = 0
while True: # メインのゲームを行うループ
print('%s Wins, %s Losses, %s Ties' % (wins, losses, ties))
while True: # プレイヤーの入力を待つループ
print('Enter your move: (r)ock (p)aper (s)cissors or (q)uit')
player_move = input('>')
if player_move == 'q':
sys.exit() # プログラムの終了
if player_move == 'r' or player_move == 'p' or player_move == 's':
break # プレイヤーの入力ループから抜ける
print('Type one of r, p, s, or q.')
# プレイヤーの手の表示
if player_move == 'r':
print('ROCK versus...')
elif player_move == 'p':
print('PAPER versus...')
elif player_move == 's':
print('SCISSORS versus...')
# コンピュータの手の表示
move_number = random.randint(1, 3)
if move_number == 1:
computer_move = 'r'
print('ROCK')
elif move_number == 2:
computer_move = 'p'
print('PAPER')
elif move_number == 3:
computer_move = 's'
print('SCISSORS')
# 勝ち/負け/引き分けを表示して記録
if player_move == computer_move:
print('It is a tie!')
ties = ties + 1
elif player_move == 'r' and computer_move == 's':
print('You win!')
wins = wins + 1
elif player_move == 'p' and computer_move == 'r':
print('You win!')
wins = wins + 1
elif player_move == 's' and computer_move == 'p':
print('You win!')
wins = wins + 1
elif player_move == 'r' and computer_move == 'p':
print('You lose!')
losses = losses + 1
elif player_move == 'p' and computer_move == 's':
print('You lose!')
losses = losses + 1
elif player_move == 's' and computer_move == 'r':
print('You lose!')
losses = losses + 1
このコードを最初から一行ずつ見ていきましょう。
import random, sys
print('ROCK, PAPER, SCISSORS')
# プレイヤーの勝ち数、負け数、引き分け数を格納する3つの変数の初期化
wins = 0
losses = 0
ties = 0
まず、randomモジュールとsysモジュールをインポートしています。あとでrandom.randint()関数とsys.exit()関数を呼び出すためです。次に、プレイヤーの勝ち数、負け数、引き分け数を格納する3つの変数を初期化しています。
while True: # メインのゲームを行うループ
print('%s Wins, %s Losses, %s Ties' % (wins, losses, ties))
while True: # プレイヤーの入力を待つループ
print('Enter your move: (r)ock (p)aper (s)cissors or (q)uit')
player_move = input('>')
if player_move == 'q':
sys.exit() # プログラムの終了
if player_move == 'r' or player_move == 'p' or player_move == 's':
break # プレイヤーの入力ループから抜ける
print('Type one of r, p, s, or q.')
このプログラムでは、whileループの中でwhileループを使っています。最初の外側のループはメインのゲームを行うループであり、ループの繰り返しがじゃんけんの繰り返しに対応します。2つ目の内側のループは、プレイヤーの入力を待ちます。r、p、s、qが有効な入力です。r、p、sはグー(石を表すrockの頭文字でr)、パー(紙を表すpaperの頭文字でp)、チョキ(はさみを表すscissorsの頭文字でs)に対応します。qは終了(quitの頭文字でq)です。終了の場合はsys.exit() を呼び出してプログラムを終了させます。プレイヤーがr、p、sのどれかを入力したら、内側のループを抜けます。それ以外の入力に対しては、r、p、s、qのいずれかを入力するように促すメッセージを表示して、ループの最初に戻ります。
# プレイヤーの手の表示
if player_move == 'r':
print('ROCK versus...')
elif player_move == 'p':
print('PAPER versus...')
elif player_move == 's':
print('SCISSORS versus...')
プレイヤーの手を画面に表示します。
# コンピュータの手の表示
move_number = random.randint(1, 3)
if move_number == 1:
computer_move = 'r'
print('ROCK')
elif move_number == 2:
computer_move = 'p'
print('PAPER')
elif move_number == 3:
computer_move = 's'
print('SCISSORS')
次にコンピュータの手をランダムに選びます。random.randint()はランダムな数を返しますから、1、2、3の整数をランダムに選んでmove_numberという名前の変数に格納します。move_numberの整数値に応じて'r'、'p'、's'という文字列のいずれかをcomputer_moveという名前の変数に格納し、そのコンピュータの手を画面に表示します。
# 勝ち/負け/引き分けを表示して記録
if player_move == computer_move:
print('It is a tie!')
ties = ties + 1
elif player_move == 'r' and computer_move == 's':
print('You win!')
wins = wins + 1
elif player_move == 'p' and computer_move == 'r':
print('You win!')
wins = wins + 1
elif player_move == 's' and computer_move == 'p':
print('You win!')
wins = wins + 1
elif player_move == 'r' and computer_move == 'p':
print('You lose!')
losses = losses + 1
elif player_move == 'p' and computer_move == 's':
print('You lose!')
losses = losses + 1
elif player_move == 's' and computer_move == 'r':
print('You lose!')
losses = losses + 1
最後に、player_move(プレイヤーの手)とcomputer_move(コンピュータの手)を比較して、勝負の結果を画面に表示します。その結果に応じて、wins(勝ち)、losses(負け)、ties(引き分け)変数の値を増やします。プログラムがこの部分に到達すると、外側のメインのループの最初に戻り、次の勝負を始めます。
このじゃんけんが気に入ったら、 The Big Book of Small Python Projects (No Starch Press, 2021)に収録した他の単純なPythonプログラムのソースコードもご覧ください。
ループを使うと、指定した条件がTrueである限り繰り返しコードを実行できます。ループから抜けたりループの最初に戻ったりするには、break文とcontinue文が使えます。
こうしたフロー制御文を活用すれば、賢いプログラムを書けます。自分で関数を作成することでもフロー制御ができますが、これは次章のテーマとします。
1. プログラムが無限ループに陥ったときにどのキーを押しますか?
2. breakとcontinueの違いを説明してください。
3. forループで、range(10)とrange(0, 10)とrange(0, 10, 1)はどう違いますか?
4. forループを使って、1から10までの数を表示するプログラムを書いてください。while ループを使って、1から10までの数を表示するプログラムを書いてください。
5. spamモジュールの中にbacon()関数があるとしたら、spamモジュールをインポートしたあとでその関数をどのように呼び出しますか?