2 if-elseとフロー制御

前章までに、個々の命令の基礎と、プログラムは一連の命令であることを学びました。しかし、週末の用事リストのように、一連の命令を単純に次々と実行することにプログラミングの真価があるのではありません。プログラムは、式の評価値に応じて、命令を飛ばしたり繰り返したり、複数の命令の中から実行する命令を選んだりできます。実際に、プログラムを最初の行から最後の行まで一行ずつ実行することなんてほとんどありません。フロー制御文によって、どの条件下でどの命令を実行するかを判定します。

フロー制御文はフローチャートに対応します。本章では、取り上げるコードに対応するフローチャートを示しています。図2-1は雨が降っているときにどうするかのフローチャートです。StartからEndまで矢印をたどります。

図 2-1:雨が降っているときにどうするかのフローチャート

フローチャートでは、始まりから終わりまでに複数の道筋があるのが普通です。コンピュータプログラムのコードも同じです。フローチャートでは、分岐点をひし形で表し、その他のステップを長方形で表し、始点と終点は角丸の長方形で表します。

フロー制御文を学ぶ前に、yesとnoをどのように表すか、Pythonのコードで分岐点をどう書くかを知っておく必要があります。ブール値(真偽値)、比較演算子、ブール演算子から始めましょう。

ブール値

整数型、浮動小数点数型、文字列型の値には無限の可能性がありますが、ブール型には2つの値しかありません。TrueとFalseの2つです。ブール型という名称は数学者のジョージ・ブールから付けられました。Pythonのコードに書く際は、 TrueとFalseをクォートで囲まず、必ずTまたはFの大文字で始めて、残りの部分は小文字で書きます。これらのブール値には引用符(クォート)がないことに注意してください。文字列値の'True'や'False'とは異なります。以下の式を対話型シェルに入力してみてください。

❶ >>> spam = True
>>> spam
True
❷ >>> true
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
NameError: name 'true' is not defined
❸ >>> False = 2 + 2
  File "<python-input-0>", line 1, in <module>
SyntaxError: can't assign to False

意図的にエラーを発生させている部分があります。ほかの値と同様に、ブール値を式の中で使えますし、変数に格納することもできます(➊)。大文字と小文字を適切に書かなかったり(➋)、TrueやFalseを変数名に使ったりすると(➌)、エラーが発生します。

比較演算子

比較演算子は、関係演算子と呼ばれることもありますが、2つの値を比較してブール値に評価します。表2-1で比較演算子をまとめました。

表2-1: 比較演算子

演算子

意味

例

==

等しい

5 == 5 は Trueです。 4 == 2 + 2 も Trueです。

!=

等しくない

1 != 2 は Trueです。 'Hello' != 'Hello' は Falseです。

<

より小さい

10 < 5 は Falseです。 1.999 < 5 は Trueです。

>

より大きい

1 + 1 > 4 + 8 は Falseです。 99 > 4 + 8 は Trueです。

<=

以下

4 <= 5 は Trueです。 5 <= 5 も Trueです。

>=

以上

5 >= 4 は Trueです。l 5 >= 5 も Trueです。

これらの演算子は値に応じてTrueまたはFalseに評価します。==と!=から試してみましょう。

>>> 42 == 42
True
>>> 42 == 99
False
>>> 2 != 3
True
>>> 2 != 2
False

両側の値が等しければ==はTrueに評価し、両側の値が等しくなければ!=はTrueに評価します。==と!=はどのデータ型に対しても使えます。

>>> 'hello' == 'hello'
True
>>> 'hello' == 'Hello'
False
>>> 'dog' != 'cat'
True
>>> True == True
True
>>> True != False
True
>>> 42 == 42.0
True
❶ >>> 42 == '42'
False

整数値と浮動小数点数値が文字列と等しくなることはありません。42 == '42'(➊)はFalseです。Pythonでは整数の42は文字列の'42'とは異なるからです。他方で、整数の42と浮動小数点数の42.0は同じだとみなされます。

<、>、<=、>=は、整数値と浮動小数点数値に対してだけ使います。

>>> 42 < 100
True
>>> 42 > 100
False
>>> 42 < 42
False
>>> eggs = 42
❶ >>> eggs <= 42
True
>>> my_age = 29
❷ >>> my_age >= 10
True

eggs <= 42(❶)やmy_age >= 10(❷)のように、変数の値に対して比較演算子を用いるのが一般的です。('dog' != 'cat'のようにリテラル値を比較しても結果はわかりきっています。)フロー制御文の箇所でさらに例を見ます。

==演算子と=演算子の違い

「等しい」を表す==はイコール記号2つで、「代入」を表す=はイコール記号1つです。これらを混同しやすいので、以下の点をはっきりさせておきます。

  • ==は2つの値が等しいかどうかを判定します。
  • =は右側の値を左側の変数に代入します。

「等しい」を表す==は、「等しくない」を表す!=と同じように、2文字です。

ブール演算子

and、or、notという3つのブール演算子を使ってブール値を比較できます。比較演算子と同様に、ブール値に評価します。and演算子から詳しく見ていきましょう。

and演算子は2つのブール値(または式)を取るので、二項ブール演算子です。and演算子は、2つのブール値が両方ともTrueの場合にだけTrueに評価します。それ以外の場合はFalseに評価します。対話型シェルでand演算子の動作を試してみましょう。

>>> True and True
True
>>> True and False
False

真理値表を見ればブール演算のすべての可能性がわかります。表2-2はand演算子の真理値表です。

表 2-2: and演算子の真理値表

式

値(計算結果)

True and True

True

True and False

False

False and True

False

False and False

False

and演算子と同様に、or演算子は2つのブール値(または式)を取るので、二項ブール演算子です。or演算子は、2つのブール値のどちらかがTrueの場合にTrueに評価します。両方ともFalseであれば、Falseに評価します。

>>> False or True
True
>>> False or False
False

表2-3の真理値表にor演算子のすべての可能性を示しています。

表 2-3:or 演算子の真理値表

式

値(計算結果)

True or True

True

True or False

True

False or True

True

False or False

False

andやorとは異なり、not演算子は1つのブール値(または式)を取ります。単項演算子です。not演算子はブール値を反転させます。

>>> not True
False
❶ >>> not not not not True
True

会話や文章で二重否定を使うのと同じように、not演算子を重ねることができます(➊)。もっとも、実際のプログラムでそのような記述をするのが正当ではない理由が決してないわけではありません(否定を重ねるとこの文のように読みにくくなります)。表2-4でnotの真理値表をしめしています。

表 2-4:not 演算子の真理値表

式

値(計算結果)

not True

False

not False

True

ブール演算子と比較演算子を同時に使う

比較演算子はブール値に評価しますから、これをブール演算子とともに使うことができます。

and演算子、or演算子、not演算子は、TrueとFalseを取るのでブール演算子と呼ばれていることを思い出してください。4 < 5はブール値そのものではありませんが、ブール値に評価される式です。対話型シェルで比較演算子を用いたブール式を試してみましょう。

>>> (4 < 5) and (5 < 6)
True
>>> (4 < 5) and (9 < 6)
False
>>> (1 == 2) or (2 == 2)
True

式は左から右へと評価していきます。順番に評価を行い、式全体を一つのブール値に評価します。(4 < 5) and (5 < 6)は次のように評価されていきます。

複数のブール演算子を一つの式で使っても構いません。

>>> spam = 4
>>> 2 + 2 == spam and not 2 + 2 == (spam + 1) and 2 * 2 == 2 + 2
True

数学演算子と同じように、ブール演算子にも優先順位があります。Pythonでは、数学演算子と比較演算子を評価してから、not演算子を評価し、次にand演算子を評価して、その次にor演算子を評価します。

フロー制御の構成要素

フロー制御文は、条件部分で始まり、節が続きます(条件部分がないことはありますが、節は必ずあります)。Python固有のフロー制御に入る前に、条件とブロックについて説明します。

条件

これまでに見てきたブール式はすべて条件とみなすことができます。条件はブール式と同じものですが、フロー制御文では特に条件と呼びます。条件は必ずTrueまたはFalseのブール値に評価されます。条件がTrueまたはFalseのいずれであるかによって、フロー制御文の動作が決まります。よって、ほぼすべてのフロー制御で条件を用います。英語で「もしこの条件が真であればこれをして、そうでなければあれをする」のように読めるコードを書くことがよくあります。「この条件が真である間はこの命令を繰り返す」のように読めるコードを書くこともあるでしょう。

コードブロック

Pythonのコードの行は、ブロックに分けられます。コード行のインデント(字下げ)によりどこが一つのブロックかがわかります。ブロックには4つのルールがあります。

  • 新しいブロックはインデントが増える場所で始まります。
  • ブロックはほかのブロックを含むことができます。
  • ブロックはインデントが減る場所で終わります。
  • コロンで終わる文の直後から新しいブロックが始まります。

ブロックはインデントによりすぐに見てわかります。以下に示す簡単なプログラムでブロックを探してみてください。

username = 'Mary'
password = 'swordfish'
if username == 'Mary':
  ❶ print('Hello, Mary')
    if password == 'swordfish':
      ❷ print('Access granted.')
    else:
      ❸ print('Wrong password.')

最初のブロック(➊)はprint('Hello, Mary')の行から始まり、それ以下の行全部です。このブロックの中に別のブロック(➋)があります。print('Access Granted.')という一行だけのブロックです。3つ目のブロック(➌)も一行です。print('Wrong password.')の行です。

プログラム実行

前章のhello.pyプログラムでは、Pythonが最初の行から順番に命令を実行しました。プログラム実行とは現在実行中の命令を示す用語です。実行ごとに画面上でソースコードの各行を指で追っていくと、その指がプログラム実行であると考えることができます。

すべてのプログラム実行が上から順に行われるとは限りません。フロー制御のあるプログラムを指で追うとするなら、条件に応じてソースコードを飛び回ります。

フロー制御文

それではフロー制御で最も重要な部分である文に進みましょう。図 2-1のフローチャートではひし形で示されていた部分で、プログラムの判定部分です。

if

フロー制御文の中でも一番よく使うのは if文です。if文の節(if文のあとに続くブロック)は、if文の条件がTrueの場合に実行されます。条件がFalseの場合は飛ばされます。

if文は、「もしこの条件が真であれば、ifに続く節のコードを実行してください」と読めます。Pythonでif文は次のように書きます。

  • ifというキーワード
  • 条件(TrueまたはFalseに評価される式)
  • コロン
  • インデントされたコードブロック(if節またはifブロックと呼ばれます)

例えば、名前がAliceかどうかを判定するコードはこのように書きます。

name = 'Alice'
if name == 'Alice':
    print('Hi, Alice.')

フロー制御文はすべて行末にコロンがあり、コードブロック(節)が続きます。この例では、if節はprint('Hi, Alice.')のブロックです。図2-2はこのコードのフローチャートです。

図 2-2:if文のフローチャート

name変数の値を'Alice'以外の文字列に変えてもう一度実行してみてください。“Hi, Alice.”が画面に表示されないはずです。その部分の実行が飛ばされたからです。

else

if節のあとにelse文が続くことがあります。else節はif文の条件がFalseである場合にだけ実行されます。else文は、「この条件が真であれば、ifに続く節のコードを実行してください。さもなければ、elseに続く節のコードを実行してください。」と読めます。else文に条件はありません。よって、else文は常に次のような形になります。

  • elseというキーワード
  • コロン
  • インデントされたコードブロック(else節またはelseブロックと呼ばれます)

Aliceの例に戻って、Aliceではない名前の人に向けたあいさつをするためにelse文を使ってみます。

name = 'Alice'
if name == 'Alice':
    print('Hi, Alice.')
else:
    print('Hello, stranger.')

図2-3はこのコードのフローチャートです。

図 2-3: else文のフローチャート

name変数の値を'Alice'以外の文字列に変えてもう一度実行してみてください。'Hi, Alice.'ではなく'Hello, stranger.'と画面に表示されるはずです。

elif

2つの節のうち1つだけを実行したい場合は、if とelseを使います。たくさんの節のうちどれか1つだけを実行したい場合があるかもしれません。elif文は “else if” という意味の文であり、if文またはelif文に続きます。それ以前の条件がすべてFalseの場合にのみ判定される条件です。elif文は常に次のような形になります。

  • elifというキーワード
  • 条件(TrueまたはFalseに評価される式)
  • コロン
  • インデントされたコードブロック(elif節またはelifブロックと呼ばれます)

elifを先ほどの名前判定プログラムに追加してみましょう。

name = 'Alice'
age = 33
if name == 'Alice':
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')

年齢を判定して12歳よりも若ければ別の内容を表示します。図2-4はこのコードのフローチャートです。

図 2-4:elif文のフローチャート

age < 12がTrueでname == 'Alice'がFalseの場合にelif節が実行されます。どちらの条件もFalseの場合は、どちらの節も飛ばされます。少なくとも1つの節の実行が保証されるわけではありません。一連のelif文に続く節のうち、一つが実行されるか一つも実行されないかです。条件がTrueになれば、残りのelif節は飛ばされます。例として、新しいファイルエディタウィンドウを開いて以下のコードをvampire.pyという名前で保存してください。

name = 'Carol'
age = 3000
if name == 'Alice':
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')
elif age > 2000:
    print('Unlike you, Alice is not an undead, immortal vampire.')
elif age > 100:
    print('You are not Alice, grannie.')

名前判定プログラムにage(年齢)によって異なる出力をするelif文を2つ追加しました。図2-5はこのコードのフローチャートです。

図 2-5:複数のelif文があるvampire.pyプログラムのフローチャート

elif 文の順番が重要です。順番を変えるとバグが入り込みます。条件がTrueになれば残りのelif節は飛ばされることを思い出してください。vampire.pyの節の順番を変えると問題が発生します。次のようにコードを変更してvampire2.pyという名前で保存してください。

name = 'Carol'
age = 3000
if name == 'Alice':
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')
❶ elif age > 100:
    print('You are not Alice, grannie.')
elif age > 2000:
    print('Unlike you, Alice is not an undead, immortal vampire.')

コード実行時に変数ageに格納されている値が3000だとしましょう。'Unlike you, Alice is not an undead, immortal vampire.'という文字列が表示されると思うかもしれません。しかし、3,000は100よりも大きく、age > 100の条件がTrueになりますから(➊)、'You are not Alice, grannie.'が表示され、残りのelif文は飛ばされます。最大でも1つの節しか実行されません。elif節では順番が重要なのです。

図2-6はこのコードのフローチャートです。age > 100とage > 2000が入れ替わっています。

図 2-6:vampire2.pyプログラムのフローチャート。Xの道を通ることは論理的にありえません。というのも、ageが2000よりも大きければ、100より大きいという条件がすでに満たされてしまっているからです。

最後のelif文のあとにelse文を入れることもできます。その場合は、どれか1つの節が確実に実行されます。if文とelif文の条件がすべてFalseであれば、else節が実行されます。if節、elif節、else節を使って名前判定プログラムを作り直してみましょう。

name = 'Carol'
age = 3000
if name == 'Alice':
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')
else:
    print('You are neither Alice nor a little kid.')

図2-7はlittleKid.pyという名前で保存した新しいコードのフローチャートです。

図 2-7: littleKid.pyプログラムのフローチャート

このコードは、「この条件が真であれば、ifに続く節のコードを実行してください。最初の条件が真ではなく次の条件が真であれば、elifに続く節のコードを実行してください。それ以外の場合は、elseに続くコードを実行してください。」と読めます。if文とelif文とelse文をいっしょに使うときには、図2-6のようなバグを生じさせないように次のルールを念頭に置いてください。if文は最初に一つだけあります。elif文はif文のあとに来ます。確実に一つの節を実行することを保証したければ、else 文で締めます。

ここまで説明してきたように、フロー制御文を使うと、プログラムを洗練させることができますが、複雑になります。安心してください、コードを書く練習を積むと、こうした複雑なコードに慣れてきます。プログラマなら誰でも、<=ではなく <と書いたせいでプログラムが動作せず1時間を無駄にしたといった経験をするものです。誰でもこうした些細なミスをします。

短いプログラム:反対言葉の日(訳注:事実と反対のことを言うことにする子どもの遊び)

ブール値とif-else文を使った以下のコードをoppositeday.pyという名前で保存してください。

today_is_opposite_day = True

# today_is_opposite_dayに応じてsay_it_is_opposite_dayを設定する
❶ if today_is_opposite_day == True:
    say_it_is_opposite_day = True
else:
    say_it_is_opposite_day = False

# 反対言葉の日ならsay_it_is_opposite_dayのブール値を反転する
if today_is_opposite_day == True:
  ❷ say_it_is_opposite_day = not say_it_is_opposite_day

# 今日が反対言葉の日かどうかを言う
if say_it_is_opposite_day == True:
    print('Today is Opposite Day.')
else:
    print('Today is not Opposite Day.')

このプログラムを実行すると、'Today is not Opposite Day.'と表示されます。このコードには2つの変数があります。プログラムの冒頭で、変数today_is_opposite_dayにTrueが格納されます。その次のif文では、その変数がTrueかどうかを判定しています(真だと判定されます)(❶)。そして、変数say_it_is_opposite_dayにTrueを格納します。このif文が偽だと判定されれば、変数say_it_is_opposite_dayFalseを格納します。2つ目のif文は変数today_is_opposite_dayがTrueかどうかを判定しています(真だと判定されます)。そしてその変数をトグル(ブール値を反転)します(❷)。最後に、3つ目のif文は変数say_it_is_opposite_dayがTrueかどうかを判定します(偽だと判定されます)。そして、真であれば'Today is Opposite Day.'と表示し、偽であれば'Today is not Opposite Day.'と表示します。

プログラムの最初の行をtoday_is_opposite_day = Falseに変えてプログラムをもう一度実行しても、'Today is not Opposite Day.'と表示されます。最初のif-else文で変数say_it_is_opposite_day にFalseが格納され、2つ目のif文の条件が偽となりその節は飛ばされ、3つ目のif文の条件が偽となるため、'Today is not Opposite Day.'が表示されます。

今日が反対言葉の日でなければ、プログラムは正しく'Today is not Opposite Day.(今日は反対言葉の日ではありません)'と表示します。今日が反対言葉の日なら、プログラムはこれまた正しく'Today is not Opposite Day.'と表示します(反対言葉の日なので事実と反対のことを言います)。論理的に考えて、変数がTrueであろうがFalseであろうが、このプログラムが'Today is Opposite Day.'と表示することは決してありません。このプログラム全体をprint('Today is not Opposite Day.')という1行に置き換えても同じプログラムです。プログラマは書いたコードの行数に応じて支払いを受けるわけではないのもうなずけます。

短いプログラム:不正直な容量計算ツール

第1章では、ハードドライブとフラッシュメモリの製造元が、異なるTBとGBの定義により、製品の容量についてうそをついていと書きました。容量がどれほど違うかを計算するプログラムを書いてみましょう。以下のコードをdishonestcapacity.pyという名前で保存してください。

print('Enter TB or GB for the advertised unit:')
unit = input('>')

# 宣伝がうそをついている容量を計算する
if unit == 'TB' or unit == 'tb':
    discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
    discrepancy = 1000000000 / 1073741824

print('Enter the advertised capacity:')
advertised_capacity = input('>')
advertised_capacity = float(advertised_capacity)

# 実際の容量を計算し、100分の1の位に丸めて、
# 結合できるように文字列に変換する
real_capacity = str(round(advertised_capacity * discrepancy, 2))

print('The actual capacity is ' + real_capacity + ' ' + unit)

このプログラムは、ユーザーにハードドライブの単位をTBかGBで入力するように求めます。

# 宣伝がうそをついている容量を計算する
if unit == 'TB' or unit == 'tb':
    discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
    discrepancy = 1000000000 / 1073741824

TBはGBよりも大きく、単位が大きければ大きいほど、宣伝されている容量と実際の容量との乖離が大きくなります。if文とelif文は、orブール演算子を使っていて、ユーザーが単位を小文字で入力しても大文字で入力しても動作するようになっています。ユーザーがそれ以外の入力をすると、if節もelif節も実行されないので、変数discrepancyへの代入は行われません。プログラムが変数discrepancyを使おうとしたときにエラーが発生します。これについてはあとで対処します。

次に、ユーザーは指定した単位で宣伝されているサイズを入力します。

# 実際の容量を計算し、100分の1の位に丸めて、
# 結合できるように文字列に変換する
real_capacity = str(round(advertised_capacity * discrepancy, 2))

この1行でたくさんのことをしています。ユーザーが単位とサイズを10TBと入力したとしましょう。この行のコードの一番内側から見ていくと、advertised_capacityとdiscrepancyのかけ算が行われています。このようにして実際の容量を計算するのですが、9.094947017729282のように小数点以下の桁数が多くなってしまいます。そこで、この数値をround()関数の第一引数として渡し、第二引数に2を指定します。このround()関数の呼び出しは、先の例で言うと、9.09を返します。これは浮動小数点数値ですが、次の行でメッセージの文字列と結合するために文字列がほしいです。そのためにstr()関数に渡します。Pythonはこの1行のコードを以下のように評価します。

ユーザーが単位としてTB、tb、GB、gb以外の入力をすると、if文の条件もelif文の条件もFalseになり、変数discrepancyは作成されません。しかし、Pythonがその存在しない変数を使おうとするまでは、ユーザーが問題に気づきません。PythonはNameError: name 'discrepancy' is not definedエラーを発生させ、real_capacityが代入される行番号を指摘します。

このバグの本当の原因は、ユーザーが不適切な単位を入力した箇所です。このエラーに対処する方法はいろいろありますが、一番簡単なのはelse節で“You must enter TB or GB”(TBかGBを入力してください)のようなメッセージを表示し、sys.exit()関数を呼び出してプログラムを終了することです(この関数については次の章で扱います)。

プログラムの最終行は、メッセージの文字列とreal_capacityとunitを結合して、ハードドライブの実際の容量を表示します。

print('The actual capacity is ' + real_capacity + ' ' + unit)

ハードドライブとフラッシュメモリの製造元は、さらにうそをついていることがあります。私は256GBのSDカードをノートパソコンのバックアップに使っています。真実のGBであれば274,877,906,944バイトになるはずですが、うそのGBだと256,000,000,000バイトです。さらに、私のコンピュータが実際に認識したのは255,802,212,352バイトです。広告されているよりも大きいサイズになるのではなく小さいサイズになるように実際のサイズがずれるのはおもしろいですね。

まとめ

条件でTrueまたはFalseに評価される式を使うと、コードのどの部分を実行してどの部分を飛ばすかを決められます。==、!=、<、>、<=、>=の比較演算子を使ってブール値に評価される式を条件として使えます。and、or、notのブール演算子を使って複数の式を組み合わせることもできます。Pythonはインデントでコードブロックを表します。この章では、if、elif、else文でブロックを作りましたが、後の章で取り上げるように、それ以外にもブロックを使う場面があります。こうしたフロー制御文を活用すれば、賢いプログラムを書けます。

練習問題

  1. ブール型の2つの値を挙げてください。また、それらはどのように表記しますか?

  2. ブール演算子を3つ挙げてください。

  3. 各ブール演算子の真理値表(ブール値とブール演算子のすべての可能な組み合わせとその結果)を書き出してください。

  4. 次の式はどのように評価されますか?

(5 > 4) and (3 == 5)
not (5 > 4)
(5 > 4) or (3 == 5)
not ((5 > 4) or (3 == 5))
(True and True) and (True == False)
(not False) or (not True)

  5. 比較演算子を6つ挙げてください。

  6. 等価演算子と代入演算子はどう違いますか?

  7. 条件とは何であり、どこで使うかを説明してください。

  8. 次のコード中で3つのブロックを判別してください。

spam = 0
if spam == 10:
    print('eggs')
    if spam > 5:
       print('bacon')
    else:
        print('ham')
    print('spam')
print('Done')

  9. spamに格納されている値が、1ならHelloを、2ならHowdyを、その他の値ならGreetings!を表示するコードを書いてください。