序章

ソフトウェア開発では、デバッグは、ソフトウェアの正常な実行を妨げる問題を探して解決するプロセスです。

Pythonデバッガーは、Pythonプログラムのデバッグ環境を提供します。 条件付きブレークポイントの設定、一度に1行ずつソースコードをステップ実行する、スタック検査などをサポートします。

前提条件

Python 3をインストールし、コンピューターまたはサーバーにプログラミング環境をセットアップする必要があります。 プログラミング環境をセットアップしていない場合は、ローカルプログラミング環境またはサーバー上のプログラミング環境のインストールおよびセットアップガイドを参照して、オペレーティングに適したものにすることができます。システム(Ubuntu、CentOS、Debianなど)

Pythonデバッガーとの対話型の作業

Pythonデバッガーは、pdbと呼ばれるモジュールとして標準のPythonディストリビューションの一部として提供されます。 デバッガーも拡張可能であり、クラスPdbとして定義されています。 詳細については、pdbの公式ドキュメントをお読みください。

情報:このチュートリアルのサンプルコードに従うには、python3コマンドを実行して、ローカルシステムでPythonインタラクティブシェルを開きます。 次に、>>>プロンプトの後に例を追加して、例をコピー、貼り付け、または編集できます。

まず、2つのグローバル変数、ネストされたループを作成する関数、およびif __name__ == '__main__':を持つ短いプログラムを使用します。 nested_loop()関数を呼び出す構造。

looping.py
num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

これで、次のコマンドを使用して、Pythonデバッガーを介してこのプログラムを実行できます。

  1. python -m pdb looping.py

-mコマンドラインフラグは、Pythonモジュールをインポートし、スクリプトとして実行します。 この場合、pdbモジュールをインポートして実行し、上記のようにコマンドに渡します。

このコマンドを実行すると、次の出力が表示されます。

Output
> /Users/sammy/looping.py(1)<module>() -> num_list = [500, 600, 700] (Pdb)

出力の最初の行には、現在のモジュール名(<module>で示されている)とそれに続く印刷された行番号(この場合は1ですが、存在する場合)が含まれます。コメントまたはその他の実行不可能な行である場合は、より大きな数値になる可能性があります)。 2行目は、pdbがデバッグ用のインタラクティブなコンソールを提供するため、ここで実行されるソースコードの現在の行を示しています。 コマンドhelpを使用してそのコマンドを学習し、help commandを使用して特定のコマンドの詳細を学習できます。 pdbコンソールはPythonインタラクティブシェルとは異なることに注意してください。

Pythonデバッガーは、プログラムの最後に到達すると自動的に最初からやり直します。 pdbコンソールを終了するときはいつでも、コマンドquitまたはexitを入力してください。 プログラム内の任意の場所でプログラムを明示的に再起動する場合は、コマンドrunを使用して再起動できます。

デバッガーを使用してプログラム内を移動する

Pythonデバッガーでプログラムを操作する場合、liststep、およびnextコマンドを使用してコード内を移動する可能性があります。 このセクションでは、これらのコマンドについて説明します。

シェル内で、コマンドlistを入力して、現在の行のコンテキストを取得できます。 上に表示したプログラムlooping.pyの最初の行(num_list = [500, 600, 700])から、次のようになります。

(Pdb) list
  1  ->	num_list = [500, 600, 700]
  2  	alpha_list = ['x', 'y', 'z']
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
  8  	        for letter in alpha_list:
  9  	            print(letter)
 10  	
 11  	if __name__ == '__main__':
(Pdb) 

現在の行は文字->で示されます。この場合、これはプログラムファイルの最初の行です。

これは比較的短いプログラムであるため、listコマンドでほぼすべてのプログラムを受け取ります。 引数を指定しない場合、listコマンドは現在の行の周囲に11行を提供しますが、次のように含める行を指定することもできます。

(Pdb) list 3, 7
  3  	
  4  	
  5  	def nested_loop():
  6  	    for number in num_list:
  7  	        print(number)
(Pdb) 

ここでは、コマンドlist 3, 7を使用して、3〜7行目を表示するように要求しました。

プログラムを1行ずつ移動するには、stepまたはnextを使用できます。

(Pdb) step
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 
(Pdb) next
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb) 

stepnextの違いは、stepは呼び出された関数内で停止するのに対し、nextは呼び出された関数を実行して次の行でのみ停止することです。現在の機能。 関数を操作すると、この違いがわかります。

stepコマンドは、関数の実行に到達するとループを繰り返し、ループが実行していることを正確に示します。最初にprint(number)で数値を出力し、次に出力を実行します。 print(letter)の文字、数字に戻るなど:

(Pdb) step
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) step
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) step
--Call--
> /Users/sammy/looping.py(5)nested_loop()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(6)nested_loop()
-> for number in num_list:
(Pdb) step
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) step
500
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
x
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
y
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb)

代わりに、nextコマンドは、段階的なプロセスを表示せずに関数全体を実行します。 exitコマンドを使用して現在のセッションを終了してから、デバッガーを再開してみましょう。

  1. python -m pdb looping.py

これで、nextコマンドを使用できます。

(Pdb) next
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) next
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) next
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) next
500
x
y
z
600
x
y
z
700
x
y
z
--Return--
> /Users/sammy/looping.py(12)<module>()->None
-> nested_loop()
(Pdb)  

コードを調べているときに、変数に渡された値を調べたい場合があります。これは、ppコマンドで実行できます。このコマンドは、pprintモジュール[を使用して式の値をきれいに出力します。 X211X]:

(Pdb) pp num_list
[500, 600, 700]
(Pdb) 

pdbのほとんどのコマンドのエイリアスは短くなっています。 stepの場合、その短縮形はsであり、nextの場合はnです。 helpコマンドは、使用可能なエイリアスを一覧表示します。 プロンプトでENTERキーを押して、最後に呼び出したコマンドを呼び出すこともできます。

ブレークポイント

通常、上記の例よりも大きなプログラムで作業するため、プログラム全体を調べるのではなく、特定の関数または行を確認することをお勧めします。 breakコマンドを使用してブレークポイントを設定することにより、指定されたブレークポイントまでプログラムを実行します。

ブレークポイントを挿入すると、デバッガーはそれに番号を割り当てます。 ブレークポイントに割り当てられた番号は、番号1で始まる連続した整数であり、ブレークポイントを操作するときに参照できます。

ブレークポイントは、次のように<program_file>:<line_number>の構文に従うことにより、特定の行番号に配置できます。

(Pdb) break looping.py:5
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)

clearと入力し、次にyと入力して、現在のすべてのブレークポイントを削除します。 次に、関数が定義されている場所にブレークポイントを配置できます。

(Pdb) break looping.nested_loop
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb) 

現在のブレークポイントを削除するには、clearと入力してから、yと入力します。 条件を設定することもできます。

(Pdb) break looping.py:7, number > 500
Breakpoint 1 at /Users/sammy/looping.py:7
(Pdb)     

ここで、continueコマンドを発行すると、number xが500より大きいと評価されたとき(つまり、600に設定されたとき)にプログラムが中断します。外側のループの2回目の反復で):

(Pdb) continue
500
x
y
z
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) 

現在実行するように設定されているブレークポイントのリストを表示するには、引数なしでコマンドbreakを使用します。 設定したブレークポイントの特殊性に関する情報を受け取ります。

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
(Pdb) 

コマンドdisableとブレークポイントの番号を使用してブレークポイントを無効にすることもできます。 このセッションでは、別のブレークポイントを追加してから、最初のブレークポイントを無効にします。

(Pdb) break looping.py:11
Breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /Users/sammy/looping.py:7
	stop only if number > 500
	breakpoint already hit 2 times
2   breakpoint   keep yes   at /Users/sammy/looping.py:11
(Pdb) 

ブレークポイントを有効にするには、enableコマンドを使用し、ブレークポイントを完全に削除するには、clearコマンドを使用します。

(Pdb) enable 1
Enabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) clear 2
Deleted breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) 

pdbのブレークポイントは、多くの制御を提供します。 いくつかの追加機能には、ignoreコマンドを使用したプログラムの現在の反復中にブレークポイントを無視すること(ignore 1のように)、commandsコマンドを使用してブレークポイントでアクションを発生させることが含まれます( command 1のように)、プログラムの実行がコマンドtbreakでポイントに最初に到達したときに自動的にクリアされる一時的なブレークポイントを作成します(たとえば、3行目の一時的なブレークの場合は次のように入力できます) tbreak 3)。

pdbをプログラムに統合する

pdbモジュールをインポートし、セッションを開始する行の上にpdb関数pdb.set_trace()を追加することで、デバッグセッションをトリガーできます。

上記のサンプルプログラムでは、importステートメントと、デバッガーに入力する関数を追加します。 この例では、ネストされたループの前に追加しましょう。

# Import pdb module
import pdb

num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']


def nested_loop():
    for number in num_list:
        print(number)

        # Trigger debugger at this line
        pdb.set_trace()
        for letter in alpha_list:
            print(letter)

if __name__ == '__main__':
    nested_loop()

コードにデバッガーを追加することで、特別な方法でプログラムを起動したり、ブレークポイントを設定したりする必要がなくなります。

pdbモジュールをインポートしてpdb.set_trace()関数を実行すると、通常どおりプログラムを開始し、デバッガーを実行して実行できます。

プログラム実行フローの変更

Pythonデバッガーを使用すると、jumpコマンドを使用して実行時にプログラムのフローを変更できます。 これにより、前方にスキップして一部のコードが実行されないようにしたり、後方に戻ってコードを再度実行したりできます。

文字列sammy = "sammy"に含まれる文字のリストを作成する小さなプログラムを使用します。

letter_list.py
def print_sammy():
    sammy_list = []
    sammy = "sammy"
    for letter in sammy:
        sammy_list.append(letter)
        print(sammy_list)

if __name__ == "__main__":
    print_sammy()

python letter_list.pyコマンドを使用して通常どおりプログラムを実行すると、次の出力が返されます。

Output
['s'] ['s', 'a'] ['s', 'a', 'm'] ['s', 'a', 'm', 'm'] ['s', 'a', 'm', 'm', 'y']

Pythonデバッガーを使用して、最初のサイクルの後に最初にジャンプして実行する方法を示しましょう。 これを行うと、forループが中断されていることがわかります。

  1. python -m pdb letter_list.py
> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 5
Breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) pp letter
's'
(Pdb) continue
['s']
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) jump 6
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
'a'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
['s']
['s', 'm']
['s', 'm', 'm']
['s', 'm', 'm', 'y']

上記のデバッグセッションでは、コードが続行されないように5行目にブレークを入れてから、コードを続行します(letterの値をきれいに出力して、何が起こっているかを示します)。 次に、jumpコマンドを使用して6行目にスキップします。 この時点で、変数letterは文字列'a'と等しく設定されていますが、それをリストsammy_listに追加するコードをジャンプします。 次に、ブレークポイントを無効にして、continueコマンドで通常どおり実行を続行します。そのため、'a'sammy_listに追加されることはありません。

次に、この最初のセッションを終了し、デバッガーを再起動してプログラム内でジャンプバックして、すでに実行されているステートメントを再実行します。 今回は、デバッガーでforループの最初の反復を再度実行します。

> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
  1  ->	def print_sammy():
  2  	    sammy_list = []
  3  	    sammy = "sammy"
  4  	    for letter in sammy:
  5  	        sammy_list.append(letter)
  6  	        print(sammy_list)
  7  	
  8  	if __name__ == "__main__":
  9  	    print_sammy()
 10  	
 11  	
(Pdb) break 6
Breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) jump 5
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
['s', 's']
['s', 's', 'a']
['s', 's', 'a', 'm']
['s', 's', 'a', 'm', 'm']
['s', 's', 'a', 'm', 'm', 'y']

上記のデバッグセッションでは、6行目にブレークを追加し、続行した後、5行目にジャンプして戻りました。 文字列's'がリストsammy_listに2回追加されていることを示すために、途中できれいに印刷しました。 次に、6行目のブレークを無効にして、プログラムの実行を続行しました。 出力には、sammy_listに追加された's'の2つの値が表示されます。

一部のジャンプは、特に未定義の特定のフロー制御ステートメントにジャンプしたりジャンプしたりするときに、デバッガーによって防止されます。 たとえば、引数が定義される前に関数にジャンプしたり、try:exceptステートメントの途中にジャンプしたりすることはできません。 finallyブロックから飛び出すこともできません。

Pythonデバッガーのjumpステートメントを使用すると、プログラムのデバッグ中に実行フローを変更して、フロー制御をさまざまな目的に変更できるかどうかを確認したり、コードで発生している問題をよりよく理解したりできます。

一般的なpdbコマンドの表

これは、Pythonデバッガーで作業するときに覚えておくと便利なpdbコマンドとその短い形式の表です。

指示 ショートフォーム それが何をするか
args a 現在の関数の引数リストを出力します
break b プログラムの実行時にブレークポイントを作成します(パラメーターが必要です)
continue cまたはcont プログラムの実行を継続します
help h 指定されたコマンドのコマンドまたはヘルプのリストを提供します
jump j 実行する次の行を設定します
list l 現在の行の周りにソースコードを印刷します
next n 現在の関数の次の行に到達するか、戻るまで実行を続けます
step s 現在の行を実行し、最初の可能な機会に停止します
pp pp 式の値をきれいに印刷します
quitまたはexit q プログラムを中止します
return r 現在の関数が戻るまで実行を続けます

コマンドとデバッガーの操作の詳細については、Pythonデバッガーのドキュメントを参照してください。

結論

デバッグは、ソフトウェア開発プロジェクトの重要なステップです。 Pythonデバッガーpdbは、Pythonで記述された任意のプログラムで使用できるインタラクティブなデバッグ環境を実装しています。

プログラムを一時停止し、変数が設定されている値を確認し、個別のステップバイステップでプログラムの実行を実行できる機能を使用すると、プログラムの実行内容をより完全に理解し、に存在するバグを見つけることができます。ロジックまたは既知の問題のトラブルシューティング。