Python float型を用いたシステムを作ろう

今回の学習内容

Python基礎講座の2回目です。
この講座の目的は1回目に記載しています。

1回目を読んでくださった方から「全くのプログラミング初心者なので説明をもう少し詳しくしてほしい」との感想をいただきました。
なのでもう少し説明を増やします。
「分かってるよ!」って人はコードまで飛んでください。

今回の学習内容は以下です。

  • 小数の使い方
  • defでメソッドを作成(メソッドについての説明は以下の解説内で行っています)

前回は整数を使いましたが、今回は小数を使います。

小数について

小数の扱いは注意しなければならないため少し説明します。

Pythonでは整数をint型、小数をfloat型と呼び、2つは別物です。

別物なので、整数と小数が混ざると計算できません。
人間:1 + 0.1 = 1.1
PC : 1 + 0.1 = ?

人間にとっては整数の計算も小数の計算あまり変わらないかもしれませんが、コンピューターにとっては異なる処理になります。
この辺りの理論は少し難しいため、徐々に理解していきましょう。
今回の講座では説明が長くなるので別の機会にするつもりです。

型についてもう少し話しておきます。

型について

Pythonは動的型付け言語です。
動的型付けと言うと難しいですが、実行時に型が決まるということです。
つまりコードを書いている時点で型を明示しません。
そのため、最初の説明でint型やfloat型と説明しましたが、変数の定義にはintもfloatも登場しません。
ですが、「じゃあ型を意識しなくて良いんだ!」とはなりません。
1 + 0.1 = ? となるように、型の扱いを間違えると意図しない挙動になってしまいます。
逆に言えば、型を明示する必要がなくとも、型を意識してコードが書けるとエラーが減らせます。
型について頭の片隅にでも置いておくことが重要です。

作成システムの仕様

正常系の仕様は以下です。

  • 1週間分の体温と体調を入力できる
  • 体温は小数点の入力を受け付ける
  • 体調は「良い」なら2、「普通」なら1、「悪い」なら0での整数で入力を受け付ける
  • 1週間の平均体温を表示する
  • 1週間の最高体温を表示し、その時の体調を表示する
    最高体温を入力した日が複数ある場合、複数の体調を表示する
    例:「最高体温は36.6で、その日の体調は良い, 普通です。」
  • 1週間の最低体温を表示し、その時の体調を表示する
    最低体温を入力した日が複数ある場合、複数の体調を表示する
    例:「最低体温は36.2で、その日の体調は普通, 普通です。」

異常系の仕様は以下です。

  • 体温に整数もしくは小数以外が入力された時、「体温が不正です。もう一度体温を測ってください。」と表示し、再度入力を受け付ける。
  • 体調に数字の0~2以外が入力された時、「入力が不正です。0~2の数字を半角で入力してください。」と表示し、再度入力を受け付ける。

正常系の結果は以下のようになります。

実行結果

体温を記録するなら小数点以下を1桁にすべきですが、あまり多くの内容を詰め込みすぎると難しくなってしまうため桁数を制限していません。
やってみたい人は小数点以下の桁数が1桁になるようにコードを変えてみてください。

前回と同様に腕試しをしたい人は、この仕様を見て自分でコードを書いてみてください。
自分でコードを書いてみて仕様通りに動いているなら、あなたのコードも正解です。
なぜなら以下の記事に書いているように、同じ結果になるコードの書き方はいくつもあるためです。

コード

DAYS = ['月','火','水','木','金','土','日']
CONDITIONS =['悪い','普通','良い']
COMMA = ','
ERROR_INPUT_CONDITION = "入力が不正です。0~2の数字を半角で入力してください。"
temperatureList = []
conditions = []

def input_temperature():
  while True:    
    input_temp = input()
    try:
      temperatureList.append(float(input_temp))
      break   
    except:
      print("体温が不正です。もう一度体温を測ってください。")
      
def input_condition():
  while True:    
    try:
      input_cond = int(input())
      if (input_cond >= 0) and (input_cond <= 2):
        conditions.append(input_cond)
        break
      else:
        print(ERROR_INPUT_CONDITION)
    except:
      print(ERROR_INPUT_CONDITION)

def get_condition_string(target_indexs):
  result_string = ""
  counter = 0
  for i in target_indexs:
    result_string += CONDITIONS[conditions[i]]
    counter += 1
    if not counter == len(target_indexs):
      result_string += COMMA
  return result_string

print("一週間の体温とその日の体調を入力してください。")
print("体調は良いなら2、普通なら1、悪いなら0を半角で入力してください。")
print('')

for day in DAYS:
    print(f"{day}曜日の体温を入力してください。")
    input_temperature()
    print(f"その日の体調を入力してください。")
    input_condition()
    print('')

max_temperatureList = [i for i, x in enumerate(temperatureList) if x == max(temperatureList)]
min_temperatureList = [i for i, x in enumerate(temperatureList) if x == min(temperatureList)]

print(f"平均体温は{sum(temperatureList) / len(temperatureList)}です。")
print(f"最高体温は{max(temperatureList)}で、その日の体調は{get_condition_string(max_temperatureList)}です。")
print(f"最低体温は{min(temperatureList)}で、その日の体調は{get_condition_string(min_temperatureList)}です。")

今回のコードのメインとなる処理は39行目からです。
それまでは定数やメソッドを定義しています。

1行目~6行目

定数と変数を定義しています。

8行目~15行目

defはメソッドを定義するために使います。

メソッドとは一連の処理をひとまとめにするものです。
メソッドを使うメリットは以下です。

  • 一連のまとまった処理を切り出すことで、コードが読みやすくなる
  • 複数個所に同じ処理を書かなくてよくなり、変更があった場合に修正箇所をメソッド1つに限定できる

修正箇所を1か所に限定できるというのは保守する際に大事なことです。
メソッドに切り出しておかなければ何か所も同じ修正をしていくことになり、とても面倒な作業をしなければならなくなります。

例えばこんなコードがあったとします。

a=2
b=3
c=5

print("処理1")
if a % 2 == 1:
    print("偶数")

print("処理2")
if b % 2 == 1:
    print("偶数")

print("処理3")
if c % 2 == 1:
    print("偶数")

if a % 2 == 1は a を2で割って余りが1なら以下の処理をするという意味です。
その後の処理で「偶数」と画面に表示していますが、ある数を2で割った時に余りが1となるのは奇数の場合です。
つまり上のコードは全てifの条件が間違えています。
このコードでは6, 10, 14行目それぞれに修正が必要となり、3か所修正することになります。

一方で、以下のコードのように共通部分の処理をメソッドにしている場合はどうでしょうか。

a=2
b=3
c=5

def isEven(val):
    return val % 2 == 1

print("処理1")
if isEven(a):
    print("偶数")    

print("処理2")
if isEven(b):
    print("偶数")

print("処理3")
if isEven(c):
    print("偶数")

偶数かどうか判定する部分を5行目のisEvenメソッドにまとめています。
全く同じ処理ですが、こちらのコードを修正する場合は6行目をreturn val % 2 == 0とするだけです。
1ヶ所の修正だけで済みました。

今回の例ではメソッドにまとめない場合も修正箇所は3か所とそこまで多くありませんが、大きめのプロジェクトになれば100か所以上に同じ処理が必要となることもあります。
そんな時にメソッドにまとめていないと退屈な修正作業を延々と行わなければならなくなってしまいます。
単調作業をやりたくない人はメソッドにまとめる習慣をつけましょう。

説明が長くなりましたが、8行目の解説に戻ります。
8行目ではinput_temperature()というメソッドを定義しています。
このメソッドの中身が9行目~15行目に書かれています。
Pythonでは処理のまとまりをインデントの深さで表現します。
そのため9行目~15行目がインデントが深くなっています。
9行目~15行目では体温の入力を受け付け、入力された値をfloat型、つまり小数にしてリストに追加しています。
while Trueの部分が分からない人は、第1回目で解説しているのでそちらを見てみてください。

17行目~27行目

体調の入力を受け付けています。
概ね8行目~15行目の体温の処理と同様です。
整数値で0~2以外の値が入力された場合にはエラーメッセージを表示します。

29行目~37行目

get_condition_stringメソッドを定義します。
このメソッドは上述の2つと異なり引数を受け取ります。
def get_condition_string(target_indexs)の「target_indexs」が引数です。
引数とはメソッドの呼び出し元から受け取る値で、受け取った値をメソッドの中で使用することができます。
どういう場合に引数があるメソッドにすれば良いかというと、だいたい似たような処理だけれど一部だけ処理を変えたい場合です。

先ほどのコードで説明すると、

a=2
b=3
c=5

def isEven(val):
    return val % 2 == 1

print("処理1")
if isEven(a):
    print("偶数")    

print("処理2")
if isEven(b):
    print("偶数")

print("処理3")
if isEven(c):
    print("偶数")

isEvenメソッドは引数valを受け取ります。
valを受け取れることによって、isEven(3) や isEven(10)のように呼び出し元で異なる値を指定すれば、都度異なる値を偶数かどうか判定できるようになります。
引数をうまく選定できると似たようなメソッドを減らすことができ保守しやすくなります。
一見地味なところですが、プログラマーとしての腕が問われます。

29行目のdef get_condition_string(target_indexs)メソッドの説明に戻ります。
このメソッドで何をしているかというと、体調に入力された0~2の値に対応する悪い~良いに変換し、変換した結果を呼び出し元に文字列として返しています。
複数の体調を取得する場合は36行目で体調の間にカンマが入るようになっており、「良い, 普通」となるようにしています。

呼び出し元に値を返す方法はreturnを使います。
37行目のreturn result_stringで文字列を呼び出し元に返しています。

39行目~最後

ここからがメインの処理になります。
今まではこのメイン処理で使う材料を準備していたイメージです。

ここでは、これまでに定義した定数やメソッドを呼び出し、必要な情報と結果を画面に表示しています。
メソッドさえ出来上がれば、あとはそのメソッドを呼び出すだけなので難しくありません。

最後に & 関連書籍

以上、第2回目でした。
分かりにくいところや「もっとこうしてほしい」などのご意見がありましたら、ツイッターから連絡いただけると幸いです。
質問に関してもお気軽にお問い合わせください。

以下の記事で応用的な内容を公開しています。
今回の内容が簡単だった人は挑戦してみてください

以下は関連書籍です。

コメント

タイトルとURLをコピーしました