3.1. 条件構文#
条件構文とは、「ある条件に応じてプログラムの動作を変えるための仕組み」です。私たちが日常的に使っているシステムの多くには、このような条件分岐が組み込まれています。たとえば、オンラインショッピングサイトでは、購入金額が一定額以上になると送料が無料になりますし、スーパーでは特定の曜日に割引が適用されるといった具合です。今日この授業に出ていない誰かも、おそらく「出席を取らないならサボる」という条件構文を心の中に書いているのかもしれません。このように、状況に応じて処理を切り替える仕組みは非常に重要です。ただ、プログラムは人間と違って迷いません。後ろめたさも言い訳もなく、ただ条件に従って淡々と動きます。冷たいけれど、そのぶん裏切りません。
3.1.1. if 文#
Python で条件判断を行う方法を、実際の例を使って見ていきましょう。「どんぐりの重さ」に基づいて、どの木から落ちたものかを判定するプログラムを作ります。まずは、クヌギ、ミズナラ、アラカシの木の下で集めたどんぐりの重さを以下の表にまとめました。
木の名前 |
どんぐりの重さ [g] |
最小値 |
最大値 |
---|---|---|---|
クヌギ |
5.28, 6.77, 5.42, 6.03, 6.00 |
5.28 |
6.77 |
ミズナラ |
2.80, 2.54, 2.28, 2.33, 2.41 |
2.28 |
2.80 |
アラカシ |
1.36, 1.42, 1.54, 1.30, 1.56 |
1.30 |
1.54 |
このデータを見ると、重さが 5.28g 以上のどんぐりはクヌギのものだとわかります。この情報をもとに、「もし重さが 5.28g 以上ならば、クヌギと判断して表示する」プログラムを作ってみましょう。
x = 5.86
tree = None
if x >= 5.28:
tree = 'kunugi'
tree
'kunugi'
このコードでは、変数 x
に保存された重さが 5.28 以上かどうかを調べています。条件が成立した場合のみ、変数 tree
に文字列 'kunugi'
が代入されます。
if 文を使った条件判定では、if
とコロン(:
)の間に条件式を書きます。 そして、その条件が成立した場合に実行する処理を、if
の次の行に書きます。 このとき、「この処理は条件が成立したときだけ実行される」ということを示すために、if
よりも文字を下げて記述します。このような文字下げのことをインデント(indent)と呼びます。インデントの方法にはいくつかのスタイルがあります。たとえば、「タブ 1 つ分」、「空白 2 つ分」、「空白 4 つ分」といった違いがあります。どの方法でも構いませんが、同じブロック内ではスタイルを統一する必要があります。なお、インデントの種類(タブか空白か)が混ざっていると、Python ではエラーの原因になります。見た目が似ているだけに気づきにくいため、最初からスタイルを決めておくのがおすすめです。
PEP8
PEP8 は、Python の公式なスタイルガイドで、コードを読みやすく保守しやすくするための書き方のルールをまとめたものです。 たとえば、インデントにはスペース 4 つを使う、関数名は小文字とアンダースコアで書く、といった具体的な指針が示されています。 PEP8 は絶対に守らなければならない決まりではありませんが、これを意識することで、わかりやすいコードを書けるようになります。
なお、条件を判定する前に tree = None
としているのは、どの条件にも当てはまらなかった場合の初期値をあらかじめ設定しておくためです。Python では、「値がまだ設定されていない」「何もない」といった状態を表す特別な値として、None
が用意されています。
None
None
は、Python で扱うデータの一つで、「何もない」ことを表します。数学では 0 や空白文字(スペース)などが「何もない」ことを示す場合もありますが、プログラミングの世界では 0 や空白も、それ自体が意味を持つ「値」として扱われます。そのため、本当に「何もない」状態を表すためには、特別な値を使う必要があります。Python では、その役割を果たすのが None
です。変数に None
を代入すると、その変数に「空の箱」が用意されますが、その中身はまだ何も入っていない状態です。たとえば、今後の処理結果を格納するために、あらかじめ空の変数を用意しておきたいときなどに None
を使います。
また、数値の大小を比較するときには、「左の値が右の値以上」の意味で比較演算子 >=
を使います。たとえば、x >= 5.28
と書けば、「x
が 5.28 以上かどうか」を判定できます。なお、>=
の順序を間違えて =>
と書いたり、>
と =
の間に空白を入れたりすると構文エラー(Syntax Error)になります。Python では記号の順序や書き方が厳密に決まっているため、こうした小さな間違いにも注意しましょう。
次に、変数 x
に 5.28 より小さい値を代入して、プログラムの動作を確認してみましょう。
x = 2.21
tree = None
if x >= 5.28:
tree = 'kunugi'
tree
x
の値が 2.61 なので、条件 x >= 5.28
は成立しません。そのため、変数 tree
には何も代入されず、初期値の None
が出力されます。
3.1.2. if-else 文#
if 文だけを使うと、ある条件が成立したときの処理は書けますが、条件が成立しなかったときの処理は定義できません。そこで、条件の成立・不成立に応じて処理を分けたいときは、if
に else
を組み合わせます。これにより、条件が成立する場合としない場合の両方に対して、それぞれ異なる処理を定義できます。
では、実際に「どんぐりの重さが 5.28g 以上なら 'kunugi'
、それ以外の場合は 'unknown'
」と表示するプログラムを作ってみましょう。
x = 2.21
tree = None
if x >= 5.28:
tree = 'kunugi'
else:
tree = 'unknown'
tree
'unknown'
この場合、入力値が重さが 5.28g 未満であるため、x >= 5.28
の条件が設立しません。そのため、if
ではなく、else
の処理が行われ、tree
に 'unknown'
が代入されます。
3.1.3. if 文の入れ子構造#
複数の条件が同時に成立したときに特定の処理を実行したい場合、if 文を入れ子構造にすることで簡単に実現できます。例えば、ユーザー名とパスワードの両方が一致したときにログインを許可したり、毎月 10 日に買い物金額が 2,000 円以上であれば割引を適用したりする場合に使います。
Table 3.1 を見ると、ミズナラのどんぐりの重さは「2.28g以上」かつ「2.80g以下」となっています。そのため、ミズナラかどうかを判定するには、この 2 つの条件を同時に満たしているかを確認する必要があります。これを Python で書くと、次のようになります。
x = 2.62
tree = None
if x >= 2.28:
if x <= 2.80:
tree = 'mizunara'
tree
'mizunara'
このように、最初の if 文で「2.28g 以上」であるかを確認し、次の if 文で「2.80g 以下」であるかをチェックしています。両方の条件が成立したときだけ、tree = 'mizunara'
の処理が実行されます。
なお、条件を入れ子構造で記述する場合は、インデントの位置を正確に揃えて、内側の if 文が外側の if 文の内部にあることを明確に示す必要があります。Python ではインデントが構文の一部となっているため、間違った字下げはエラーや意図しない動作の原因になるので注意しましょう。
Python には、複数の条件を同時に判定できる論理演算子(logical operator)があります。2 つ以上の条件を「かつ(and
)」や「または(or
)」でつなげることで、入れ子構造を使わずに条件を一行で書くことができます。ミズナラを判定する場合、「2.28g 以上」かつ「2.80g 以下」という条件を and
を使って結ぶと、次のように簡潔に記述できます。
x = 2.62
tree = None
if (x >= 2.28) and (x <= 2.80):
tree = 'mizunara'
tree
'mizunara'
このように論理演算子 and
を利用すると、条件が多くなってもコードが見やすくなり、管理しやすくなります。
3.1.4. if-elif 文#
次に、複数種類のどんぐりを判定するプログラムを作ってみましょう。Table 3.1 の表を見ると、クヌギ、ミズナラ、アラカシの 3 種類のどんぐりは、それぞれ重さの範囲が異なることがわかります。そこで、if 文を複数組み合わせて、重さの値からどんぐりの種類を判定するプログラムを書いてみましょう。これにより、重さの範囲に応じて適切な種類名を出力できます。
木の名前 |
どんぐりの重さ [g] |
最小値 |
最大値 |
---|---|---|---|
クヌギ |
5.28, 6.77, 5.42, 6.03, 6.00 |
5.28 |
6.77 |
ミズナラ |
2.80, 2.54, 2.28, 2.33, 2.41 |
2.28 |
2.80 |
アラカシ |
1.36, 1.42, 1.54, 1.30, 1.56 |
1.30 |
1.54 |
x = 2.46
tree = None
if (5.28 <= x) and (x <= 6.77):
tree = 'kunugi'
if (2.28 <= x) and (x <= 2.80):
tree = 'mizunara'
if (1.30 <= x) and (x <= 1.54):
tree = 'arakashi'
tree
'mizunara'
上のプログラムでは、変数 x
の値に対して複数の if 文を使い、それぞれの条件ごとにどんぐりの判定を行っています。
ひとつの値に対して複数の条件判定を行う方法としては、複数の if 文を使うほかに、次のように 2 つ目以降の条件を elif 文でつなげる方法もあります。elif 文を使うと、条件は上から順に評価され、最初に成立した条件の処理が実行された時点で、残りの条件はスキップされます。これにより、処理が効率的になり、条件の重複判定も防ぐことができます。
x = 2.46
tree = None
if (5.28 <= x) and (x <= 6.77):
tree = 'kunugi'
elif (2.28 <= x) and (x <= 2.80):
tree = 'mizunara'
elif (1.30 <= x) and (x <= 1.54):
tree = 'arakashi'
tree
'mizunara'
さらに、このプログラムに、どの条件にも当てはまらなかった場合に 'unknown'
を出力するため、else
を追加しましょう。
x = 1.74
tree = None
if (5.28 <= x) and (x <= 6.77):
tree = 'kunugi'
elif (2.28 <= x) and (x <= 2.80):
tree = 'mizunara'
elif (1.30 <= x) and (x <= 1.54):
tree = 'arakashi'
else:
tree = 'unknown'
tree
'unknown'
エキスパートシステム(expert system)という言葉をご存知でしょうか。エキスパートシステムとは、特定の分野における専門家の知識や経験をコンピュータに取り込み、それを活用して問題を解決するシステムのことです。ここまで作成したプログラムは、どんぐりの重さをもとに種類を判定しているため、簡単なエキスパートシステムの一例といえます。
しかし、エキスパートシステムには一つの欠点があります。それは、一度プログラムを作成すると、あとから内容を修正するのが難しい点です。この問題を解決するために、どんぐりの重さの閾値(判定の境界となる数値)をプログラムに直接書き込むのではなく、データに基づいて自動的に閾値を計算する方法を考えてみましょう。これにより、どんぐりのデータが追加・更新されてもプログラムの修正が不要となり、常に最新のデータに基づいた正確な判定が可能になります。
では、プログラムにどんぐりの重さのデータを読み込ませて、最大値と最小値を自動で取得できるように変更してみましょう。ただし、実際にファイルからデータを読み込む処理を加えると作業が複雑になるため、ここではデータを辞書としてプログラム内に直接書き込む形にとどめます。これにより、データを手軽に扱いながら、最大値・最小値を自動で計算する方法を理解できます。
acorn_weights = {
'kunugi': [5.28, 6.77, 5.42, 6.03, 6.00],
'mizunara': [2.80, 2.54, 2.28, 2.33, 2.41],
'arakashi': [1.36, 1.42, 1.54, 1.30, 1.56]
}
x = 1.74
tree = None
if (min(acorn_weights['kunugi']) <= x) and (x <= max(acorn_weights['kunugi'])):
tree = 'kunugi'
elif (min(acorn_weights['mizunara']) <= x) and (x <= max(acorn_weights['mizunara'])):
tree = 'mizunara'
elif (min(acorn_weights['arakashi']) <= x) and (x <= max(acorn_weights['arakashi'])):
tree = 'arakashi'
else:
tree = 'unknown'
tree
'unknown'
さて、データが増え、ミズナラとアラカシのどんぐりの重さの範囲が重複する場合、この判定システムがどう動作するかを確認しましょう。
acorn_weights = {
'kunugi': [5.28, 6.77, 5.42, 6.03, 6.00, 6.82, 4.51, 3.78],
'mizunara': [2.80, 2.54, 2.28, 2.33, 2.41, 1.85, 2.60, 1.66],
'arakashi': [1.36, 1.42, 1.54, 1.30, 1.56, 1.24, 1.84, 1.71]
}
x = 1.74
tree = None
if (min(acorn_weights['kunugi']) <= x) and (x <= max(acorn_weights['kunugi'])):
tree = 'kunugi'
elif (min(acorn_weights['mizunara']) <= x) and (x <= max(acorn_weights['mizunara'])):
tree = 'mizunara'
elif (min(acorn_weights['arakashi']) <= x) and (x <= max(acorn_weights['arakashi'])):
tree = 'arakashi'
else:
tree = 'unknown'
tree
'mizunara'
入力値が 1.74 の場合、データに基づいて判定すると、ミズナラとアラカシの両方の可能性があります。しかし、if-elif 文では条件が上から順に評価され、最初に一致した条件の処理を実行した時点で他の条件はスキップされるため、この場合は 'mizunara'
が変数 tree
に代入されます。
もし該当するすべてのどんぐりの種類を記録したい場合は、if-else 文ではなく、複数の独立した if 文を使う必要があります。これにより、条件に合致する複数の種類をすべて取得できます。では、条件に一致するどんぐりの種類をすべて記録できるよう、プログラムを修正してみましょう。
acorn_weights = {
'kunugi': [5.28, 6.77, 5.42, 6.03, 6.00, 6.82, 4.51, 3.78],
'mizunara': [2.80, 2.54, 2.28, 2.33, 2.41, 1.85, 2.60, 1.66],
'arakashi': [1.36, 1.42, 1.54, 1.30, 1.56, 1.24, 1.84, 1.71]
}
x = 1.74
tree = ''
if (min(acorn_weights['kunugi']) <= x) and (x <= max(acorn_weights['kunugi'])):
tree = tree + 'kunugi'
if (min(acorn_weights['mizunara']) <= x) and (x <= max(acorn_weights['mizunara'])):
tree = tree + 'mizunara'
if (min(acorn_weights['arakashi']) <= x) and (x <= max(acorn_weights['arakashi'])):
tree = tree + 'arakashi'
tree
'mizunaraarakashi'
これでプログラムは、複数の候補がある場合にも柔軟に対応できるようになりました。if-else 文と複数の独立した 文を適切に使い分けることが重要です。ひとつの値に対して判定する場合でも、if-else 文を使うと一部の候補が取りこぼされる可能性があります。
次に、判定結果を格納する変数 tree
をリストに変更し、複数の候補を分かりやすく表示できるようにしてみましょう。まず、空のリストを作成します(tree = []
)。その後、各 if 文で条件を判定し、成立した場合は .append()
メソッドを使って候補をリストに追加していきます。
たとえば、以下のようになります。
acorn_weights = {
'kunugi': [5.28, 6.77, 5.42, 6.03, 6.00, 6.82, 4.51, 3.78],
'mizunara': [2.80, 2.54, 2.28, 2.33, 2.41, 1.85, 2.60, 1.66],
'arakashi': [1.36, 1.42, 1.54, 1.30, 1.56, 1.24, 1.84, 1.71]
}
x = 1.74
tree = []
if (min(acorn_weights['kunugi']) <= x) and (x <= max(acorn_weights['kunugi'])):
tree.append('kunugi')
if (min(acorn_weights['mizunara']) <= x) and (x <= max(acorn_weights['mizunara'])):
tree.append('mizunara')
if (min(acorn_weights['arakashi']) <= x) and (x <= max(acorn_weights['arakashi'])):
tree.append('arakashi')
tree
['mizunara', 'arakashi']
このように、プログラムから固定値を取り除くことができました。データが更新されても、プログラムを変更することなく最新のデータを反映し、どんぐりの種判定を行えるようになりました。
広い意味では、機械学習の分野に足を踏み入れたことになります。とはいえ、重さが微妙に重なる場合には、候補を並べるだけで確率的な判断はまだできません。ここから先に待ち受けているのは、線形代数、確率統計、微積分学、…と格闘し続ける日々、そして「結局どんぐりなんて、リスが気に入るかどうかで決まります」という虚無感です。でも安心してください、今ならまだ戻れます。ここは潔く機械学習をスルーして、Python の基礎に戻りましょう。泥沼にはまるのは、あなたが「分類」される側になる覚悟ができてからでも遅くはありません。
練習問題 SI-1
正の整数が入力された時、それが偶数なら 'even'
、奇数なら 'odd'
を出力するプログラムを作成してください。
x = 2
msg = None
print(msg)
練習問題 SI-2
入力された数値が偶数なら 'even'
、奇数なら 'odd'
を出力するプログラムを作成してください。なお、正の整数以外の値が入力された場合に、'not supported'
を出力してください。
x = 2
msg = None
print(msg)
練習問題 SI-3
西暦(正の整数)を受け取り、閏年ならば True
、平年ならば False
を出力するプログラムを作成してください。なお、閏年は次の規則によって設定されています。
西暦年が 4 で割り切れる年は、次の例外を除き、閏年である。
100 で割り切れる年は、次の例外を除き、平年である。
400 で割り切れる年は、閏年である。
x = 2100
is_leapyr = None
print(is_leapyr)
練習問題 SI-4
2 つの要素を持つリストが与えられたとき、リストの要素を昇順に並べかえるプログラムを作成してください。なお、組み関数 sorted
およびリストの sort
メソッドなどの関数を利用しないこと。
x = [8, 6]
print(x)
練習問題 SI-5
BMI は成人の肥満度を表す指数であり、体重 \(w\) [kg] および身長 \(h\) [m] を用いて次式のように計算できます。
日本では、BMI の値に応じて、低体重、標準、肥満、高度肥満のように分類しています。そこで、体重と身長を与えると、低体重、標準、肥満、高度肥満のいずれかを出力するプログラムを作成してください。
BMI |
肥満度 |
---|---|
18.50 未満 |
低体重 |
18.50 以上〜 25.0 未満 |
標準 |
25.00 以上〜 35.0 未満 |
肥満 |
35.00 以上 |
高度肥満 |