5 デバッグ

ここまでに基本的なプログラムの書き方を学びました。そろそろ単純ではないバグがプログラムで見つかってもおかしくありません。本章では、なるべく苦労せずに素早くバグの根源的な原因を突き止めて修正するのに役立つツールとテクニックを説明します。プログラマの間で古くから言われている冗談のように言うなら、プログラミングの90パーセントはコードを書き、残りの90パーセントはデバッグです。

コンピュータは指示されたことしかしません。意図を推測してくれることはありません。プロのプログラマでもしょっちゅうバグを作り込んでいます。だから自分が書いたプログラムに問題があっても意気消沈しないでください。

幸いなことに、ちょっとしたツールとテクニックを使えば、コードが何をしていてどこでおかしくなるかを正確に突き止められます。デバッガという機能をMuで使います。プログラムの命令を一つずつ実行して、コードの実行中に変数に格納されている値を検査し、プログラムが進むにつれて値がどのように変化するかを追跡できます。プログラムを最大の速度で実行するよりも遅くなりますが、ソースコードから値を推測するのではなく、プログラム実行中の実際の値を確かめられます。

エラーを示す例外を調整して送出することにも取り組み、バグを早期に発見する助けとなるログとアサーションという2つの機能についても説明します。一般に、バグを早く発見すればするほど、修正するのは容易になります。

例外の送出

不適切なコードを実行しようとしたときに、Pythonは例外を送出します。第4章では、プログラムが想定される例外から回復できるように、try 文とexcept文でPythonの例外を処理しました。自分で独自の例外を送出することもできます。例外を送出することは、「このコードを停止して、プログラムの実行をexcept文に移してください」と伝えるようなものです。

raise文で例外を送出できます。次のような形になります。

  • raiseというキーワード
  • Exception()関数の呼び出し
  • Exception()関数に渡すエラーメッセージの文字列

対話型シェルで次のように入力してみてください。

>>> raise Exception('This is the error message.')
Traceback (most recent call last):
  File "<pyshell#191>", line 1, in <module>
    raise Exception('This is the error message.')
Exception: This is the error message.

例外を送出するraise文をカバーするtry文とexcept文がなければ、プログラムはクラッシュして例外のエラーメッセージを表示します。

例外をどのように処理するかを決めるのは、関数そのものではなく、raise文を含む関数呼び出しのコードである場合がよくあります。raise文が関数内にあり、try文とexcept文が関数呼び出しの部分にあるということです。例として、次のコードをboxPrint.pyという名前で保存してください。

def box_print(symbol, width, height):
    if len(symbol) != 1:
      ❶ raise Exception('Symbol must be a single character string.')
    if width <= 2:
      ❷ raise Exception('Width must be greater than 2.')
    if height <= 2:
      ❸ raise Exception('Height must be greater than 2.')

    print(symbol * width)
    for i in range(height - 2):
        print(symbol + (' ' * (width - 2)) + symbol)
    print(symbol * width)

try:
    box_print('*', 4, 4)
    box_print('O', 20, 5)
    box_print('x', 1, 3)
    box_print('ZZ', 3, 3)
❹ except Exception as err:
  ❺ print('An exception happened: ' + str(err))
try:
    box_print('ZZ', 3, 3)
except Exception as err:
    print('An exception happened: ' + str(err))

文字(記号)と幅と高さを引数に取り、その文字で指定された幅と高さの箱を表示するbox_print()関数を定義しました。この箱が画面に表示されます。

この関数は、1文字だけを受け取り、幅と高さは2より大きくなければならないとしましょう。これらの条件が満たされない場合には例外を送出するようにif文を書きました。それから、いろいろな引数でbox _print()関数を呼び出し、不適切な引数があればtry-exceptが処理します。

このプログラムは、except文でexcept Exception as errと書いています(❹)。box_print()からExceptionオブジェクトが返されたら、このexcept文はerrという名前の変数にそのオブジェクトを格納します(❶ ❷ ❸)。そのExceptionオブジェクトを str()関数に渡せば文字列に変換でき、ユーザーに示すエラーメッセージを得られます(❺)。このboxPrint.pyを実行すると、出力は次のようになります。

****
*  *
*  *
****
OOOOOOOOOOOOOOOOOOOO
O                  O
O                  O
O                  O
OOOOOOOOOOOOOOOOOOOO
An exception happened: Width must be greater than 2.
An exception happened: Symbol must be a single character string.

try文とexcept文を使えば、プログラム全体をクラッシュさせずに、エラーを美しく処理できます。

アサーション

アサーションは健全性チェックです。コードが明らかにおかしな振る舞いをしていないか確かめます。assert文で実行します。健全性チェックに引っかかると、AssertionErrorエラーが送出されます。assert文は次のような形になります。

  • assertというキーワード
  • 条件(TrueまたはFalseに評価される式)
  • カンマ
  • 条件がFalseの場合に表示する文字列

「私はこの条件が真であるとassert(断言)します。もし真でないなら、どこかにバグがあるので、プログラミングをここで停止します」と読めます。例えば、次のコードを対話型シェルに入力してください。

>>> ages = [26, 57, 92, 54, 22, 15, 17, 80, 47, 73]
>>> ages.sort()
>>> ages
[15, 17, 22, 26, 47, 54, 57, 73, 80, 92]
>>> assert ages[0] <= ages[-1]  # agesの最初の要素が最後の要素以下

このassert 文はagesの最初の要素が最後の要素以下であると断言しています。sort()にバグがなく正しく動作するなら、このアサーションは真になるという健全性チェックです。ages[0] <= ages[-1]という式はTrueに評価されますから、assert文は何も行いません。

コードにバグがある場合を試してみましょう。sort()ではなくreverse()を呼び出します。対話型シェルで次のように入力すると、assert文がAssertionErrorを送出します。

>>> ages = [26, 57, 92, 54, 22, 15, 17, 80, 47, 73]
>>> ages.reverse()
>>> ages
[73, 47, 80, 17, 15, 22, 54, 92, 57, 26]
>>> assert ages[0] <= ages[-1]  #  agesの最初の要素が最後の要素以下
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
AssertionError

例外処理の場合とは異なり、assert文をtryとexceptで処理してはいけません。assertが失敗すれば、プログラムはクラッシュすべきです。このように「早く失敗する」ようにすれば、バグの原因を作り込んでからバグを発見するまでの時間を短縮できます。バグの原因を探してチェックしなければならないコードの量を減らすことができるのです。

アサーションはプログラマにとってのエラーなのであって、ユーザーにとってのエラーではありません。アサーションは開発中の間だけ失敗すべきものです。完成したプログラムの中でユーザーの目に触れさせるものではありません。(ファイルが見つからないとかユーザーが不適切なデータを入力したといった)プログラムの通常実行時に発生するエラーについては、assert文ではなく例外を送出すべきです。

ログ

プログラムの実行中に変数の値を出力しようとコード中でprint()関数を使っていたら、デバッグのために一種のログを使っていることになります。 ログはプログラムで何がどういう順番で起こっているかを把握するための優れた方法です。Pythonのloggingモジュールを使うと、自由自在にメッセージを記録できます。ログメッセージには、実行日時とその時点での変数の値を記載します。そうすればいつプログラムがおかしくなったのかを追跡して理解する手がかりが得られます。ログが記録されていなければ、その部分のコードが飛ばされて実行されなかったことがわかります。

loggingモジュール

プログラムの実行時にログメッセージを画面に表示できるように、loggingモジュールを有効化します。以下のコードをコピーしてプログラムの冒頭に貼り付けてください。

import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s -  %(levelname)s -  %(message)s')

loggingモジュールのbasicConfig()関数により、記録するログのレベルとフォーマットを指定します。

階乗を計算する関数を書いているとします。4の階乗は1 × 2 × 3 × 4で24になります。7の階乗は1 × 2 × 3 × 4 × 5 × 6 × 7で5,040になります。新しいファイルエディタのタブを開いて次のコードを入力してください。バグがありますが、どこがおかしいのかを理解する助けとなるログメッセージが表示されます。このプログラムをfactorialLog.pyという名前で保存してください。

import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s -  %(levelname)s -  %(message)s')
logging.debug('Start of program')

def factorial(n):
    logging.debug('Start of factorial(' + str(n) + ')')
    total = 1
    for i in range(n + 1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
    logging.debug('End of factorial(' + str(n) + ')')
    return total

print(factorial(5))
logging.debug('End of program')

ログ情報を表示するためにlogging.debug()関数を使いました。debug()関数はbasicConfig()を呼び出し、その関数で指定したフォーマットで、debug()関数に渡したメッセージとともに、情報を出力します。プログラム中でprint(factorial(5))を呼び出すと、ログメッセージを無効にしている場合でも計算結果を表示します。

このプロブラムを実行すると、次のように出力されます。

2035-05-23 16:20:12,664 - DEBUG - Start of program
2035-05-23 16:20:12,664 - DEBUG - Start of factorial(5)
2035-05-23 16:20:12,665 - DEBUG - i is 0, total is 0
2035-05-23 16:20:12,668 - DEBUG - i is 1, total is 0
2035-05-23 16:20:12,670 - DEBUG - i is 2, total is 0
2035-05-23 16:20:12,673 - DEBUG - i is 3, total is 0
2035-05-23 16:20:12,675 - DEBUG - i is 4, total is 0
2035-05-23 16:20:12,678 - DEBUG - i is 5, total is 0
2035-05-23 16:20:12,680 - DEBUG - End of factorial(5)
0
2035-05-23 16:20:12,684 - DEBUG - End of program

factorial()関数は5の階乗として0を返します。 これは正しい計算結果ではありません。forループは1から5の数値をかけ算してtotalに格納すべきところですが、logging.debug()によるログメッセージに示されていますように、変数iが1ではなく0から始まっています。ゼロに何をかけてもゼロですから、totalの値が間違っています。

for i in range(n + 1):の行をfor i in range(1, n + 1):に変えて、プログラムをもう一度実行してください。出力は次のようになります。

2035-05-23 17:13:40,650 - DEBUG - Start of program
2035-05-23 17:13:40,651 - DEBUG - Start of factorial(5)
2035-05-23 17:13:40,651 - DEBUG - i is 1, total is 1
2035-05-23 17:13:40,654 - DEBUG - i is 2, total is 2
2035-05-23 17:13:40,656 - DEBUG - i is 3, total is 6
2035-05-23 17:13:40,659 - DEBUG - i is 4, total is 24
2035-05-23 17:13:40,661 - DEBUG - i is 5, total is 120
2035-05-23 17:13:40,661 - DEBUG - End of factorial(5)
120
2035-05-23 17:13:40,666 - DEBUG - End of program

factorial(5)の呼び出しが正しく120という値をを返しました。ログメッセージがループ中の過程を示しています。そのおかげでバグに対処しやすくなりました。

logging.debug()を呼び出すと、その関数に渡された文字列だけでなく、タイムスタンプとDEBUGという語も表示されていることがわかります。

ログファイル

ログメッセージを画面に表示するのではなく、テキストファイルに書き出すこともできます。logging.basicConfig()関数は、以下のように、filenameという名前付きパラメータを取ります。

import logging
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG,
format=' %(asctime)s -  %(levelname)s -  %(message)s')

このコードは、ログメッセージをmyProgramLog.txtというファイルに保存します。

ログメッセージは便利ですが、画面を散らかしてプログラムの出力が読みにくくなることがあります。ログメッセージをファイルに書き出すと、画面はきれいなままになり、プログラムの実行後でもログメッセージを読めるようになります。メモ帳やテキストエディットなどのエディタで、ログを記録したこのテキストファイルを開けます。

print()でのデバッグは避けるべき

import loggingとlogging.basicConfig(level=logging.DEBUG, format= '%(asctime)s - %(levelname)s - %(message)s')を書くのはやや不格好です。その代わりにprint()呼び出しを使おうと思うかもしれませんが、その誘惑に屈してはいけません。デバッグが終わったあとに、ログメッセージを記録する箇所ごとにprint() 呼び出しを取り除くのに時間がかかるのがオチです。ログメッセージではないprint()呼び出しを誤って削除してしまうこともあるかもしれません。ログメッセージの利点は、何箇所でログを記録しようとも、logging.disable(logging.CRITICAL)という1行を加えれば、いつでもログを無効化できることです。print()とは違って、loggingモジュールではログメッセージの表示/非表示の切り替えが簡単です。

ログメッセージは、ユーザーのためではなく、プログラマのために記録するものです。デバッグするのに必要な辞書の内容をユーザーは知る必要がありません。そうした用途にはログメッセージを使ってください。File not found(ファイルが見つかりません)やInvalid input, please enter a number(入力が無効です、数値を入力してください)のようなユーザーが目にするエラーメッセージには、print()呼び出しを使ってください。問題を解決するのに役立つ情報をユーザーに見せるようにしてください。

ログレベル

ログメッセージを重要度で分類するのにログレベルを活用できます。プログラムのテスト中に重要ではないログメッセージを非表示にできます。表5-1に重要度が低い順で示しているように、5段階のログレベルがあります。レベルごとに関数があります。

表 5-1: Pythonのログレベル

Level

Logging function

DEBUG

logging.debug()

最も低いレベルで、ちょっとした詳細情報を示します。問題を診断するときにだけこのメッセージを気にするのが一般的です。

INFO

logging.info()

プログラム中の一般的なイベント情報を記録したり、チェックポイントが機能していることを確かめるために使います。

WARNING

logging.warning()

現在はプログラムの動作を妨げないものの将来的には妨げる可能性のある潜在的な問題を示すために使います。

ERROR

logging.error()

プログラムの実行を失敗させるエラーを記録します。

CRITICAL

logging.critical()

最も高いレベルで、プログラム全体を停止させる致命的なエラーを示すために使います。

ログメッセージをどのレベルに分類するかは自分次第です。ログメッセージを文字列としてこれらの関数に渡します。対話型シェルで次のように入力してみてください。

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - 
%(levelname)s -  %(message)s')
>>> logging.debug('Some minor code and debugging details.')
2035-05-18 19:04:26,901 - DEBUG - Some debugging details.
>>> logging.info('An event happened.')
2035-05-18 19:04:35,569 - INFO - The logging module is working.
>>> logging.warning('Something could go wrong.')
2035-05-18 19:04:56,843 - WARNING - An error message is about to be logged.
>>> logging.error('An error has occurred.')
2035-05-18 19:05:07,737 - ERROR - An error has occurred.
>>> logging.critical('The program is unable to recover!')
2035-05-18 19:05:45,794 - CRITICAL - The program is unable to recover!

ログレベルを活用すると、表示するログメッセージを変更できます。basicConfig()関数のlevelという名前付きパラメータにlogging.DEBUGを渡すと、すべてのログレベルのメッセージが表示されます(DEBUGは一番重要度が低いので)。プログラム開発中に、エラーだけを知りたくなったとします。その場合は、basicConfig()のlevel引数にlogging.ERRORを設定します。そうするとERRORとCRITICALのメッセージだけが表示され、DEBUGとINFOとWARNINGのメッセージは表示されません。

ログの無効化

プログラムのデバッグを終えたら、こうしたログメッセージで画面を散らかしたくはないでしょう。logging.disable()関数はログメッセージを無効化するので、ログの呼び出しを手動で取り除く必要はありません。logging.disable()にログレベルを渡せば、そのレベル以下のログメッセージを抑制できます。ログをすべて無効化するなら、logging.disable(logging.CRITICAL)を実行します。対話型シェルで次のように入力してみてください。

>>> import logging
>>> logging.basicConfig(level=logging.INFO, format=' %(asctime)s - 
%(levelname)s -  %(message)s')
>>> logging.critical('Critical error! Critical error!')
2035-05-22 11:10:48,054 - CRITICAL - Critical error! Critical error!
>>> logging.disable(logging.CRITICAL)
>>> logging.critical('Critical error! Critical error!')
>>> logging.error('Error! Error!')

logging.disable()はその後のログメッセージを無効化するので、コードのimport logging行の直後に書くことが多いでしょう。そうすれば、その行をコメントアウトするかコメントを外すかして、ログメッセージを必要に応じて有効化/無効化できます。

Muのデバッガ

MuエディタやIDLEその他のエディタには、プログラムを1行ずつ実行できるデバッガ機能があります。デバッガはコードを1行実行し、そこで待ってくれます。このようにデバッガでプログラムを実行すると、プログラムの任意の地点でじっくりと時間をかけて変数の値を確認できます。これはバグを追跡するのにとても役立つツールです。

Muのデバッガでプログラムを実行するには、画面上部の実行ボタンの隣にあるデバッグボタンをクリックします。デバックインスペクタ枠がウィンドウの右側に開きます。この枠にはプログラム中の現在の変数の値が一覧表示されます。図5-1は、デバッガが1行目の実行前にプログラムの実行を停止した画面です。1行目がファイルエディタで強調表示されているのがわかります。

図 5-1:Muのデバッガでプログラムを実行している画面

デバッグモードになると、継続、ステップオーバー、ステップイン、ステップアウトという新しいボタンが表示されます。通常実行時と同様に、停止ボタンも使えます。

継続ボタンをクリックすると、終了するかブレークポイントに達するまで通常と同じようにプログラムを実行します(プレイクポイントについてはあとで説明します)。デバッグを終えてプログラムを通常と同じように実行したければ、継続ボタンをクリックします。

ステップインボタンをクリックすると、デバッガはコードの次の行を実行してから停止します。デバッガが実行する行が関数呼び出しなら、その関数に入り、その関数の1行目に移動します。

ステップオーバーボタンをクリックすると、ステップインボタンと同じように、コードの次の行を実行します。次の行が関数呼び出しの場合、ステップオーバーボタンは関数のコードを一気に実行します。関数のコードは最速で実行され、デバッガは関数呼び出しが終わったところで停止します。例えば、次の行がspam()関数の呼び出しで、この関数内のコードを気にしないなら、関数を通常の速度で実行して呼び出しが終わったところで停止させるために、ステップオーバーをクリックします。このように、ステップオーバーボタンのほうがステップインボタンよりもよく使います。

ステップアウトボタンをクリックすると、デバッガは現在の関数呼び出しが終わるまで最速で実行します。ステップインボタンで関数呼び出しに入ってしまったけれどもその関数から早く出たければ、現在の関数呼び出しから抜け出すためにステップアウトボタンをクリックします。

デバッグを終了して残りのプログラムを実行する必要がなければ、停止ボタンをクリックします。プログラムが即座に終了します。

足し算プログラムのデバッグ

Muのデバッガを使う練習をしてみましょう。新しいファイルエディタのタブを開いて次のコードを入力してください。

print('Enter the first number to add:')
first = input()
print('Enter the second number to add:')
second = input()
print('Enter the third number to add:')
third = input()
print('The sum is ' + first + second + third)

buggyAddingProgram.py という名前で保存し、まずはデバッガなしで実行してみてください。次のように出力するはずです。

Enter the first number to add:
5
Enter the second number to add:
3
Enter the third number to add:
42
The sum is 5342

プログラムはクラッシュしませんでしたが、合計値は明らかにおかしいです。

次はデバッガでプログラムを実行します。デバッグボタンをクリックすると、プログラムが1行目で停止します。次はこの行を実行します。

ステップオーバーボタンを一度クリックし、最初のprint()呼び出しを実行します。ここではステップインではなくステップオーバーを使います。print()関数のコードに入って行きたくないからです(仮にステップインを使ったとしても、Muのデバッガがprint()のようなPythonの組み込み関数の中に入ることはないはずですが)。図5-2に示したように、デバッガが2行目に進み、その行がファイルエディタで強調表示されます。このようにプログラム実行が今どこにあるのかがわかります。

図 5-2:ステップオーバーをクリックした後のMuエディタウィンドウ

もう一度ステップオーバーをクリックし、input()関数呼び出しを実行します。出力ウィンドウでinput()呼び出しに何か入力するまで強調表示がなくなります。5を入力してエンターキーを押すと、強調表示が戻ってきます。

ステップオーバーを何度かクリックして、3と42を次の2つの数値として入力します。デバッガが7行目の最後のprint()呼び出しに達すると、Muウィンドウが図5-3のようになっているはずです。

図 5-3:Muのエディタウィンドウの右側に表示されているデバッグインスペクタ枠を見ると、変数が整数ではなく文字列になっていて、これがバグの原因だとわかります。

デバッグインスペクタ枠では、変数first、second、thirdが5、3、42という整数値ではなく、'5'、'3'、'42'という文字列値に設定されていることがわかります。最終行が実行されると、Pythonはこれらの数値を合計するのではなく文字列を結合するので、バグが発生します。

プログラムをデバッガで1行ずつ実行するのはバグ探しに役立ちますが、時間がかかります。コードの特定の行までプログラムを通常と同じように実行したい場合が多いでしょう。ブレークポイントを設定すると、それを実現できます。

ブレークポイントの設定

コードの特定の行にブレークポイントを設定すると、プログラム実行がその行に達したときにデバッガを停止させられます。新しいファイルエディタのタブを開いて、以下のプログラムを保存してください。1,000回のコイン投げをシミュレートします。coinFlip.pyという名前で保存してください。

import random
heads = 0
for i in range(1, 1001):
  ❶ if random.randint(0, 1) == 1:
        heads = heads + 1
    if i == 500:
      ❷ print('Halfway done!')
print('Heads came up ' + str(heads) + ' times.')

random.randint(0, 1)の呼び出し(❶)は、半々の確率で0と1を返します。1が表(head)だと考えれば、コイン投げをシミュレートできます。このプログラムをデバッガなしで実行すると、以下のような出力がすぐに表示されます。

Halfway done!
Heads came up 490 times.

このプログラムをデバッガで実行すると、プログラムが終了するまでに何千回もステップオーバーボタンをクリックしなければなりません。プログラム実行のちょうど半分の時点(1,000回のうち500回コインを投げた時点)でのheadsの値を知りたいとしたら、print('Halfway done!')の行にブレークポイントを設定します(❷)。ファイルエディタの行番号をクリックすると、ブレークポイントを設定できます。図5-4のようにブレークポイントを示す赤丸が表示されます。

図 5-4:ブレークポイントを設定すると行番号の右に赤丸が表示されます。

if文はループの反復ごとに実行されるので、if文の行にブレークポイントを設定しないように気をつけてください。if文の行ではなくif文の中の行にブレークポイントを設定すると、if節に入ったときだけ停止します。

デバッガでプログラムを実行すると、ブレークポイントを設定していないときと同じように1行目で停止しますが、継続をクリックすると、ブレークポイントが設定された行に達するまで最速で実行されます。それから、継続、ステップオーバー、ステップイン、ステップアウトに進むことができます。

ブレークポイントを削除したい場合は、もう一度行番号をクリックしてください。赤丸がなくなり、以後はデバッガがそこで中断しなくなります。

まとめ

アサーション、例外、ログ、デバッガは、プログラム中のバグを発見し修正するのに使える便利なツールです。Pythonではassert文で行うアサーションにより、「健全性チェック」を実現できます。必要な条件が満たされない場合に、早期に警告を発してくれます。アサーションにより発生するエラーから回復しようとしてはいけません。早期に失敗することに意味があるのです。回復すべきエラーについては例外を用います。

例外はtry文とexcept文により捕捉して処理します。loggingモジュールを利用すると実行中のプログラムを調べられ、print()関数を使うよりもずっと便利です。ログはレベルに応じて表示/非表示を切り替えられますし、テキストファイルに書き出すこともできます。

デバッガを使うとプログラムを1行ずつ実行できます。あるいは、プログラムを通常の速度で実行し、設定したブレークポイントに到達したときに停止させられます。デバッガでは、任意の時点での変数の値の状態を確認できます。

どれほどコーディングの経験を積もうとも、意図せずバグを混入させる事態は避けられません。この章で紹介したデバッグツールとテクニックがきっと役立つでしょう。

練習問題

  1. 変数spamが10より小さい整数だとAssertionErrorを発生させるassert文を書いてください。

  2. 変数eggsとbaconが同じ文字列( 'hello'と'hello'や'goodbye'と'GOODbye'が同じだと判定されるように、大文字と小文字を区別しません)だとAssertionError を発生させるassert文を書いてください。

  3. 常にAssertionErrorを発生させるassert文を書いてください。

  4. logging.debug()を呼び出すために必要な2行を書いてください。

  5. logging.debug()がprogramLog.txtという名前のファイルにログメッセージを書き込むのに必要な2行を書いてください。

  6. ログレベルの5段階を挙げてください。

  7. プログラム中のすべてのログメッセージを無効化するために必要なコードを書いてください。

  8. 同じメッセージを表示するのにprint()よりもログメッセージを利用するのが望ましいのはなぜですか?

  9. ステップオーバーとステップインとステップアウトの違いを説明してください。

10. 継続をクリックすると、デバッガはどこで停止しますか?

11. ブレークポイントとは何ですか?

12. Muでブレークポイントを設定する方法を説明してください。

練習プログラム:コイン投げのデバッグ

以下のプログラムは、コイン投げの結果を推測するシンプルなゲームです。プレイヤーは二択で推測します(簡単なゲームですね)。しかし、このプログラムには複数のバグがあります。プログラムの正常動作を妨げているバグを見つけ出してください。

import random
guess = ''
while guess not in ('heads', 'tails'):
    print('Guess the coin toss!Enter heads or tails:')
    guess = input()
toss = random.randint(0, 1)  # 0は裏(tails)、1は表(heads)
if toss == guess:
    print('You got it!')
else:
    print('Guess again!')
    guess = input()
    if toss == guess:
        print('You got it!')
    else:
        print('You are really bad at this game.')