『退屈なことはPythonにやらせよう』その8(10章:デバッグ)

Python初心者です。『退屈なことはPythonにやらせよう』を走っています。

8回目の今回は、10章:デバッグ をやっていきます~。

前回の記事はこちら↓

今回の目次

「デバッグ」といえば、「New Game!(!)」でねねっちがやってたあのイメージですね!

何でコードが思った通りに動いてくれないのかを見つける、大事な武器になりますので、気合をいれて臨みたいと思います。


例外を起こす・assert文

例外を起こすキーワード「raise」

pythonでコードを書く際に、pythonやライブラリでサポートされているエラー意外にも、自分で書いた関数中でこういう値で回して欲しくないという場合があるかと思います。

そんな時に便利なのがraise文です。

使い方としては

raise Exception (“エラー時のメッセージ”)

という形になります。

具体的には、確認したい変数を if 文などに入れることで、望んだ値になっていなかったりした場合にraise文に当たるようにします。

すると、設定したエラーメッセージを表示し、プログラムの実行が中断されます。

また、tracebackモジュールを用いて、トレースバックの内容をテキストファイルに書き出すことができます。

assert文

assert文では、コードが間違った挙動をしていないかチェックします。

使い方としては

assert 条件式 , “条件式がFalseの時に表示する文字列”

となります。「,」も必須です。

ユーザーから値を受け取る変数で、間違ってたらいけない変数の値をチェックするために使うことができますかね。

アサートを無効化するには、コマンドラインでのコード実行時に「-O」をつけます。

py -O pythonファイル名

みたいな感じですね。


デバッグにはprintよりもloggingモジュールがベター

logging モジュールを使うためのコード

print文では、ユーザーに見て欲しい文字を使い、デバッグ用の表示(変数の中身の確認など)はloggingモジュールを用いるのが良いようです。具体的なメリットとしては、

  • メッセージ表示のon-offが簡単にできる
  • ユーザーに見て欲しいprint文を誤って消してしまうことが防げる

などが分かりやすいです。他にも、デバッグのレベルを設定したりすることもできます。
(コードを書けば書くほど、この辺の重要性が光ってきそうな気がしていますが、まだ分からないです。)

loggingモジュールを使うには

#! python3
import logging
logging.basicConfig(level = logging.DEBUG, format = " %(asctime)s - %(levelname)s - %(message)s")
logging.debug("ロギング開始で表示したい文字列など")

#logging表示のon-off
logging.disable(logging.CRITICAL) #コメントアウトするとoff

という感じになります。

levelには5段階あり、重要なほうから

  • CRITICAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG

となっています。ただ、これらのどれを使うかは、コードを書く人の匙加減みたいです。

ログをファイルに出力する場合には、最初のコードに”filename”の引数として、テキストファイルなどを渡せばOKです。

#! python3
import logging
logging.basicConfig(filename = "log_of_debug.txt", level = logging.DEBUG, 
                    format = " %(asctime)s - %(levelname)s - %(message)s")
logging.debug("ロギング開始で表示したい文字列など")

#logging表示のon-off
logging.disable(logging.CRITICAL) #コメントアウトするとoff

logging.basicConfigの引数としてfilenameを指定すると以上のようになります。

デバッグをしっかりこなして、つよいエンジニアになりたいですね。


演習プロジェクト:コイン投げゲームのバグ発見

課題の内容

以下のコードで与えられた「コイン投げゲーム」から、バグを見つけ出し修正することが今回の課題です。

#! python3

import random
guess = ""
while guess not in ("表", "裏"):
    print("コインの裏表を当ててください。表か裏を入力してください: ")
    guess = input()
toss = random.randint(0, 1) #0は裏、1は表
if toss == guess:
    print("当たり!")
else:
    print("はずれ!もう一回当てて!")
    guess = input()
    if toss == guess:
        print("当たり!")
    else:
        print("はずれ。このゲームは苦手ですね")

実際に動かしてみると、

  • 一度目の入力ができない
  • そもそも、0と1しか持っていないため、裏と表に変換できていない。

というところが真っ先にわかります。

そこで、以下のようなにコードを追加してみました。

#! python3
# check10.py  -  コイン投げゲームのプログラム(バグ発見用)

import random
import logging
#logging.disable(logging.CRITICAL)

#デバッグ用
logging.basicConfig(level=logging.DEBUG,
format = " %(asctime)s - %(levelname)s - %(message)s")
logging.debug("プログラム開始")

#課題コード  -  コイン投げプログラム
guess = ""
logging.debug("Guess1は: {} ".format(guess))
while guess not in ("表" or "裏"):
    print("コインの裏表を当ててください。表か裏を入力してください。")
    guess = str(input())
    continue

logging.debug("Guess2は{}".format(guess))
toss = random.randint(0, 1)  #0が裏、1が表
toss_dict = {0 : "裏", 1 : "表"}
logging.debug("Guess3は{}, toss1は{}".format(guess, toss))

if toss_dict[toss] == guess:
    print("当たり")
    logging.debug("Guess4は{}".format(guess))
else:
    print("はずれ!もう一回当てて!")
    guess = input()
    logging.debug("Guess5は{}".format(guess))
    if toss_dict[toss] == guess:
        print("当たり")
    else:
        print("はずれ。このゲームは苦手ですね。")

変更点としては

  • loggingモジュールを追加
  • 辞書を使って、0と1で渡したtossからguessを結び付ける
  • logging.debugを使って、どこで変数の受け渡しがうまくいってないのか突き止める。

ここまでで、Guess3が空のまま、1回目の判定が行われていることが分かりました…

ということで、whileループをif文に変えたりして、きちんとguessに値が格納されるようにしました。

#! python3
# check10.py  -  コイン投げゲームのプログラム(バグ発見用)

import random
import logging
#logging.disable(logging.CRITICAL)

#デバッグ用
logging.basicConfig(level=logging.DEBUG,
format = " %(asctime)s - %(levelname)s - %(message)s")
logging.debug("プログラム開始")

#課題コード  -  コイン投げプログラム
guess = ""
logging.debug("Guess1は: {} ".format(guess))
if guess != ("表" or "裏"):
    print("コインの裏表を当ててください。表か裏を入力してください: ")
    guess = str(input())

logging.debug("Guess2は{}".format(guess))

#tossとguessの入力を結び付ける
toss = random.randint(0, 1)  #0が裏、1が表
toss_dict = {0 : "裏", 1 : "表"}
logging.debug("Guess3は{}".format(guess))

#guess == "表" or "裏"

#ゲーム開始
if toss_dict[toss] == guess:
    print("当たり")
    logging.debug("Guess4は{}".format(guess))
else:
    print("はずれ!もう一回当てて!")
    guess = input()
    logging.debug("Guess5は{}".format(guess))
    if toss_dict[toss] == guess:
        print("当たり")
    else:
        print("はずれ。このゲームは苦手ですね。")

"""
出力は以下の通り
 2020-05-11 02:33:15,558 - DEBUG - プログラム開始
 2020-05-11 02:33:15,559 - DEBUG - Guess1は:
コインの裏表を当ててください。表か裏を入力してください:
表     #ユーザー入力
 2020-05-11 02:33:16,612 - DEBUG - Guess2は表
 2020-05-11 02:33:16,612 - DEBUG - Guess3は表
はずれ!もう一回当てて!
裏     #ユーザー入力
 2020-05-11 02:33:19,670 - DEBUG - Guess5は裏
当たり
"""

という感じで、ちゃんとゲームが成り立つようになりました!

ログの表示をなくすと出力は

"""
コインの裏表を当ててください。表か裏を入力してください:
表
はずれ!もう一回当てて!
裏
当たり
"""

となって、さわやかな感じになりましたね!

デバッグのレベルの匙加減などを学びながら、より速くエラーを解決できるようにしていきたいですね。


感想

今回は割りと頑張れました。

とりあえず、printでログを出力するのは今日でもう卒業したいと思います。

次回のコードからも積極的にloggingモジュールを使って、まずは何も見なくても最初のコードが書けるくらいには使いこなせるようになりたいところです。

次の記事はこちら↓

『退屈なことはPythonにやらせよう』その8(10章:デバッグ)” に対して1件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です