みなくんの日記

やる気が皆無で自由気ままに生きてる人のブログ

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その2

今回の目標

  • [Chapter.2] ユーザ入力処理の実装
  • 例外処理によるバグの対策をしよう

[Chapter.2]ユーザ入力処理

今回作成する4Numbersゲームでは、ユーザが正解だと思う数字4桁を入力し、それを正解と比較してヒントを出すという流れになっている以上、ユーザ入力を受け付けなければなりません。今回はこの部分の実装を行うことにします。
ユーザからの入力を得るためには、input()を用いる必要があります。しかし、これ1行書くだけでユーザが入力できるので、コードを書く側からすれば非常に便利であるといえます。
そして、input()の()内には入力する際のコメントを書くことができるため、input("何か入力してください")なんてしておけば、ユーザに入力を求めるコメントを表示することが出来ちゃうわけです。
このinput文の返り値はユーザが入力した文字列であるため、何か変数に入れておけばユーザが考えた4桁の数字を取り出すことができそうです。
ということでインタプリタを使って軽く試してみましょう。

>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:1234 #ユーザが"1234"と入力した
>>> print(User_number)
1234

といった感じで、とても簡単にユーザ入力処理を行うことができるわけです。
しかし困ったことに、ユーザというのは自由気ままに動き回る、言わば決まったアルゴリズムに従っていない存在であるため、予期せぬ行動をする可能性があります。ユーザが素直に4桁の数字を入力してくれればいいのですが…。少し、インタプリタを使ってこんなテストをしてみましょう。

>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:12345566 #ユーザが4桁以上の数字を入力した
>>> print(User_number)
12345566 #そのまま出力されてしまう
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:みなくん #数字ではない文字を入力した
>>> print(User_number)
みなくん #文字列もそのまま出力される
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:↓ #そのままEnterキーを押した
>>> print(User_number)
 #何も表示されない 
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:-123 #ユーザが負の4桁の数字を入力した
>>> print(User_number)
-123 #そのまま出力されてしまう
>>>

ユーザが言うことを聞かなかった場合、何もしないと4桁の数字ではないモノまでもが変数に格納されてしまうわけです。これを放置すると、ユーザが正の4桁以外の数字を入れたときに正解と比較できずエラーが発生する要因となるため無視はできない問題です。
ではどう対処するのか。そこで登場するのが例外処理です。
ある特定の例外が発生した場合の処理…ここで言うところの、4桁の数字以外が入力された場合の処理を書いてしまおう、というもの。
この例外処理をするには、Pythonではtry・except文を用います。
try文中に書く処理は「とりあえずやりたい動作」。今回で言うところの「ユーザ入力」がこれに当たります。 except文は、簡単に言うと「try文中の処理をしてて問題が発生したとき、○○の場合はこんな処理をするよ!」ということを明記するのに使います。今回は「ユーザが正の4桁の数字以外のものを入れた場合の処理」を書くわけです。
で、肝心の「例外」の指定なのですが、今回は数字を入力させるわけなので、「ValueError」という例外を用います。これは簡単に言えば文字列をintに変換しちゃった場合に発生する例外。他の例外のバンドルについて気になった人は調べてみてください。色々あって面白いです(投げやり)
言葉で説明しても「ふーん」ってなると思うので、ちょっとしたコードを書いて試してみましょう。

>>> try: #とりあえずやりたい処理を記述
...     number=int(input("Enter some number:")) #数字を入れてもらうよう頼む
... except ValueError: #例外発生時の処理(数字を入れてくれなかった場合)
...     print("Error:not a number")
...
Enter some number:a #言うことを聞かないユーザなので'a'を入れちゃう
Error:not a number #ちゃんと怒られる
>>>

ちなみに、input()を使う際にint()を使っています。これは変数やinputで入力されたものをint型に変換させるやつで、先述したようにValueErrorの発生条件は「文字列をintに変換した場合」であるため使っているわけです。
では実際にコードを…と行きたいところですが、これだけではまだ不足。もう少し、工夫が必要です。今使った例外処理は「ValueError」。文字列ならこれで処理出来ますが、4桁より上(未満)の桁の数字だったら?負の数字だったら?――この場合について、考えていきましょう。
4桁かどうかの判別だけなら簡単です。len()を用いて、"if len(number)==4:"とするだけ。4桁でないならまずここではじかれるので、4桁の数字、文字列以外はすべてこの段階ではじくことができます。問題はそれを例外として処理しなければならないこと。ifではじいたはいいけれど、4桁未満の数字であった場合…ValueErrorは動作しません。理由は先述した通り。数字はint型変換が正常に出来てしまうので、ValueErrorが動かないんです。ということは、何も手を施さなければ例外処理をしてくれないということ。それでは困るので、一文だけ手を施してあげましょう。それはraise文の使用です。これは例外を発生させるために使います。もっと本当は深く意味があると思うのですが、ここではその程度の理解で使うこととします。実際にどのような書き方をするのか、それは以下の通り。

try:
    number=int(input("enter 4 numbers:"))
    if len(number)==4:
        print("OK!")
    else:
        raise ValueError
except ValueError:
    print("Error:not a numuber or not 4 numbers")

raise文をelse文中に使うことで、「4桁じゃないならValueErrorとして例外処理するよ!」と明記することができるようになります。
次に負の数について考えてみましょう。負の4桁、といえば「-1234」です。これなら、文字数は5になるため、上記のif文上では「4桁ではない」とはじくことができるでしょう。しかし、負の3桁だったら「-123」です。これだと、文字数が4となってしまい、if文は通過してしまうわけです。そもそも、負の桁なんて正解桁に設定していないわけですから…「-」がついてたら困りますね。なので、if文として「最初に"-"がついてたら例外処理する」という風にしましょう。ユーザが入力した値は配列として扱うことができるので、「if number[0]=="-"」とするだけ。この条件を満たしていたら、"raise ValueError"で例外処理をさせてしまいましょう。これで、考え得る例外の処理を行えたことになります。
では、コードを書く前の説明はこの程度にして実際にユーザ入力と例外処理のプログラムコードを書いてみましょう。

class input_number:
    def input_num(self):
        while True: #4桁の数字かGive upが入力されるまでループ処理
            try: 
                num=input("正解だと思う正の数字4桁を入力してください:")
                if len(num)==4: #4桁であった場合
                    check=int(num) #それが文字列であった場合、ここでValueErrorに飛ばす
                    if num[0]=="-": #負の数であった場合
                        raise ValueError #それはエラーとして処理
                    return num #何もなければユーザの入力を返り値にする
                else: #4桁以上の数字であった場合
                    raise ValueError
            except ValueError: #文字列が入力された場合(今回は4桁以上の数字もこのエラー処理になる)
                if num=="Give up": #Give upと入力された場合
                    print("諦めます。また挑戦してくださいね!")
                    exit()
                else: #他の文字列もしくは4桁以上の数字が入力された場合
                    print("4桁の正の数字かGive upのみを入力してください。")
    def start_init(self):
        input("開始するにはEnterキーを押してください...")

#---test seciton---

hoge=input_number()
hoge.start_init()
print (hoge.input_num())

#---test end----

先述したものに加え、何か色々味付けをしているのが分かると思います。コメント文を観たら理解できるかもしれませんが、一応味付けしてるところの説明はここで軽くしておきます。
まず、whileループをinput_numメソッド中全体に使用しています。これはユーザが正しい値を入力するまでループさせるために使用。
そして、プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その1で記載していた「ユーザが"Give up"と入力したら諦められるようにする」という処理をこっそり追加しています。
"Give up"は文字列であるため、処理させるのであればexcept文の例外処理中に行うのが一番楽です。なので、except文の最初にif文でその処理をさせ、プログラムを終了させています。Give up以外は再入力を求める文章を表示して終わりに。このような書き方だけで、数字/文字列の判別、桁数の判別、"Give up"機能の追加までが行えます。ちなみに、test sectionを付けた状態でこのプログラムを実行させた結果が以下の通りになります。
実行結果

正解だと思う正の数字4桁を入力してください:a 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:aaaa 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:-123 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12-3 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12345 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:1234
1234

実行結果(Give upを使った)

正解だと思う正の数字4桁を入力してください:GIve up 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:Give up 
諦めます。また挑戦してくださいね!

ちゃんと動作していることが確認できました。これで、今回の目標は達成できたことになります!

まとめ

  • ユーザ入力処理の実装を行うことができた
  • 例外処理を使って考え得るバグの対策ができた
  • 入力処理はユーザが素直じゃないことを想定したプログラミングが必要だった!

こういうのをやってると、他のウェブサイトやソフトウェアで同じような場面を見かけたときに「あぁ…これユーザがこんな入力したらちゃんと例外処理するようにしてるんだなあ。大変だろうなあ」と思ってしまったり。僕だけなんでしょうか。街中で7セグメントLEDをみたり、ボタンを押したりするときに「これ、こういうプログラムで動いてるんだろうな…」とか考えたりするの。誰か共感してくれたら嬉しい……。

次回はユーザの回答と正解の比較を行う処理についてやっていきます。