Pdbを使用したPythonデバッグ

Pdbを使用したPythonデバッグ

アプリケーションのデバッグは、望ましくないアクティビティになる場合があります。 あなたは時間の制約の下で仕事をするのに忙しく、ただそれを機能させたいだけです。 ただし、新しい言語機能を学習したり、新しいアプローチを試したりして、何かがどのように機能しているかをより深く理解したい場合もあります。

状況に関係なく、コードのデバッグは必要なので、デバッガで快適に作業することをお勧めします。 このチュートリアルでは、Pythonのインタラクティブなソースコードデバッガーであるpdbの基本的な使い方を紹介します。

pdbの一般的な使用方法をいくつか紹介します。 このチュートリアルをブックマークしておくと、後で必要になったときにすぐに参照できます。 pdb、およびその他のデバッガーは不可欠なツールです。 デバッガが必要なときは、代用品はありません。 本当に必要です。

このチュートリアルの終わりまでに、デバッガーを使用してアプリケーション内の変数の状態を確認する方法がわかります。 また、アプリケーションの実行フローをいつでも停止および再開できるため、コードの各行が内部状態にどのように影響するかを正確に確認できます。

これは、見つけにくいバグを追跡するのに最適で、障害のあるコードをより迅速かつ確実に修正できます。 pdbのコードをステップ実行し、値がどのように変化するかを確認することは、実際の目を見張るものであり、時々「顔の掌」とともに「あは」の瞬間につながります。

pdbはPythonの標準ライブラリの一部であるため、常にそこにあり、使用できます。 使い慣れたGUIデバッガーにアクセスできない環境でコードをデバッグする必要がある場合、これは命を救うことができます。

このチュートリアルのサンプルコードでは、Python 3.6を使用しています。 これらの例のソースコードは、https://github.com/natej/pdb-basics [GitHub]にあります。

このチュートリアルの最後に、link:#essential-pdb-commands [Essential pdb Commands]のクイックリファレンスがあります。

デバッグ中にチートシートとして使用できる印刷可能なpdbコマンドリファレンスもあります。

無料ボーナス: link:#[ここをクリックして印刷可能な「pdbコマンドリファレンス」(PDF)を入手してください。これは机上に置いてデバッグ中に参照できます。

はじめに:変数の値を印刷する

この最初の例では、変数の値をチェックする最も単純な形式でpdbを使用する方法を見ていきます。

デバッガーに侵入する場所に次のコードを挿入します。

import pdb; pdb.set_trace()

上記の行が実行されると、Pythonは停止し、次に何をすべきかを指示するのを待ちます。 「(Pdb)」プロンプトが表示されます。 これは、インタラクティブデバッガーで一時停止し、コマンドを入力できることを意味します。

Python 3.7以降、https://realpython.com/python37-new-features/#the-breakpoint-built-in [デバッガーに入る別の方法があります]。 PEP 553は、組み込み関数 `+ breakpoint()+`について説明しています。これにより、デバッガーに入るのが簡単で一貫性のあるものになります。

breakpoint()

デフォルトでは、上記のように、 `+ breakpoint()`は ` pdb `をインポートし、 ` pdb.set_trace()`を呼び出します。 ただし、 ` breakpoint()`を使用するとより柔軟になり、APIおよび環境変数 ` PYTHONBREAKPOINT `を使用してデバッグ動作を制御できます。 たとえば、環境で ` PYTHONBREAKPOINT = 0 `を設定すると、 ` breakpoint()`が完全に無効になり、デバッグが無効になります。 Python 3.7以降を使用している場合は、 ` pdb.set_trace()`の代わりに ` breakpoint()+`を使用することをお勧めします。

コマンドラインからPythonを直接実行し、オプション「+ -m pdb + `。 アプリケーションがコマンドライン引数を受け入れる場合、ファイル名の後に通常どおりにそれらを渡します。 例えば:

$ python3 -m pdb app.py arg1 arg2

多くのpdbコマンドが利用可能です。 このチュートリアルの最後に、リンクのリストがあります:#essential-pdb-commands [Essential pdb Commands]。 とりあえず、 `+ p `コマンドを使用して変数の値を出力しましょう。 `(Pdb)`プロンプトで ` p variable_name +`を入力して、その値を出力します。

例を見てみましょう。 これが `+ example1.py +`ソースです:

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')

これをシェルから実行すると、次の出力が得られます。

$ ./example1.py
>/code/example1.py(5)<module>()
-> print(f'path = {filename}')
(Pdb)

_ コマンドラインから実行するサンプルや独自のコードを取得するのに問題がある場合は、https://dbader.org/blog/how-to-make-command-line-commands-with-python [How Do I Pythonを使用して独自のコマンドラインコマンドを作成しますか?] Windowsを使用している場合は、https://docs.python.org/3.6/faq/windows.html [Python Windows FAQ]を確認してください。 _

ここで、「+ p filename +」と入力します。 君は見るべきだ:

(Pdb) p filename
'./example1.py'
(Pdb)

シェルでCLI(コマンドラインインターフェイス)を使用しているため、文字と書式に注意してください。 必要なコンテキストを提供します:

  • `> +`は1行目を開始し、現在のソースファイルを示します。 ファイル名の後に、括弧内に現在の行番号があります。 次は関数の名前です。 この例では、関数内およびモジュールレベルで一時停止されていないため、 ` <module>()+`が表示されます。

  • `-> +`は2行目を開始し、Pythonが一時停止する現在のソース行です。 この行はまだ実行されていません。 この例では、これは上の「> 」行の「 example1.py 」の行「+5」です。

  • `(Pdb)`はpdbのプロンプトです。 コマンドを待っています。

コマンド「+ q +」を使用して、デバッグを終了して終了します。

式を印刷する

印刷コマンド「+ p +」を使用する場合、Pythonによって評価される式を渡します。 変数名を渡すと、pdbは現在の値を出力します。 ただし、実行中のアプリケーションの状態を調査するには、さらに多くのことができます。

この例では、関数 `+ get_path()`が呼び出されます。 この関数で何が起こっているのかを調べるため、 ` pdb.set_trace()+`の呼び出しを挿入して、戻る直前に実行を一時停止しました。

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head


filename = __file__
print(f'path = {get_path(filename)}')

これをシェルから実行すると、出力が得られます:

$ ./example2.py
>/code/example2.py(10)get_path()
-> return head
(Pdb)

ここはどこ?

  • +> +:関数 `+ get_path()`の行 ` 10 `のソースファイル ` example2.py `にあります。 これは、変数名を解決するために ` p +`コマンドが使用する参照フレームです。 現在のスコープまたはコンテキスト。

  • +→ +:実行は `+ return head `で一時停止しました。 この行はまだ実行されていません。 これは、上記の「> 」行から、関数「 get_path()」の「 example2.py 」の行「+10」です。

いくつかの式を印刷して、アプリケーションの現在の状態を見てみましょう。 最初にコマンド「+ ll +」(ロングリスト)を使用して、関数のソースをリストします。

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path
<function get_path at 0x100760e18>
(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb)

有効なPython式を評価用に `+ p +`に渡すことができます。

これは、デバッグ中に、実行時にアプリケーションで代替実装を直接テストする場合に特に役立ちます。

コマンド + pp +(プリティプリント)を使用して、式をプリティプリントすることもできます。 これは、大量の出力を持つ変数または式を印刷する場合に役立ちます。 リストと辞書。 きれいに印刷すると、オブジェクトが許容される幅に収まらない場合はオブジェクトを1行に保持したり、複数行に分割したりできます。

コードのステップ実行

デバッグ時にコードをステップ実行するために使用できる2つのコマンドがあります。

Command Description

n (next)

Continue execution until the next line in the current function is reached or it returns.

s (step)

Execute the current line and stop at the first possible occasion (either in a function that is called or in the current function).

_ 「+ unt 」という名前の3番目のコマンドがあります(それまで)。 ` n +`(次)に関連しています。 このチュートリアルの後半のセクションリンク:#continuing-execution [Continuing Execution]で説明します。 _

+ n +(次)と + s +(ステップ)の違いは、pdbが停止する場所です。

+ n +(次)を使用して、次の行まで実行を継続し、現在の関数内に留まります。 外部関数が呼び出されても停止しません。 次に、「ローカルに滞在する」または「ステップオーバー」と考えてください。

+ s +(ステップ)を使用して現在の行を実行し、外部関数が呼び出された場合は停止します。 ステップを「ステップイン」と考えてください。 別の関数で実行が停止した場合、「+ s 」は「-Call-+」を出力します。

+ n +`と `+ s +`は、現在の関数の終わりに達したときに実行を停止し、 `+→の後の次の行の終わりに戻り値とともに -Return- `を出力します。 + `。

両方のコマンドを使用した例を見てみましょう。 これが `+ example3.py +`ソースです:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head


filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

これをシェルから実行して「+ n +」と入力すると、出力が得られます:

$ ./example3.py
>/code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) n
>/code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb)

「+ n 」(次)で、次の行「+15」で停止しました。 「+ <module>()」で「ローカルに留まり」、「 get_path()」の呼び出しを「ステップオーバー」しました。 現在はモジュールレベルであり、別の関数内で一時停止されていないため、関数は ` <module>()+`です。

`+ s +`を試してみましょう:

$ ./example3.py
>/code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
>/code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb)

+ s +(ステップ)では、関数 `+ get_path()`の行 ` 14 `で呼び出されたため、関数 ` get_path()`の行 ` 6 `で停止しました。 ` s `コマンドの後にある `-Call-+`の行に注目してください。

便利なことに、pdbは最後のコマンドを記憶しています。 多くのコードをステップ実行する場合は、[。keys]#Enter#を押すだけで最後のコマンドを繰り返すことができます。

以下は、「+ s 」と「 n 」の両方を使用してコードをステップスルーする例です。 関数「 get_path()」に「ステップイン」して停止するため、最初に「 s 」を入力します。 次に、「 n 」を1回入力して他の関数呼び出しを「ローカルに」または「ステップオーバー」し、[。keys]#Enter#を押して、最後のソース行に到達するまで「 n +」コマンドを繰り返します。

$ ./example3.py
>/code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
>/code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
>/code/example3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb)
>/code/example3.py(9)get_path()
-> return head
(Pdb)
--Return--
>/code/example3.py(9)get_path()->'.'
-> return head
(Pdb)
>/code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb)
path = .
--Return--
>/code/example3.py(15)<module>()->None
-> print(f'path = {filename_path}')
(Pdb)

行「-Call-」と「-Return-」に注意してください。 これは、実行が停止した理由を知らせるpdbです。 関数が戻る前に、 + n +(次)と + s +(ステップ)が停止します。 そのため、上の「-Return-」行が表示されます。

また、上記の最初の `-Return-`の後の行末にある `+→ '。' +`にも注意してください。

--Return--
>/code/example3.py(9)get_path()->'.'
-> return head
(Pdb)

戻る前に関数の最後でpdbが停止すると、戻り値も出力されます。 この例では、「」。「」です。

ソースコードのリスト

コマンド + ll +(ロングリスト:現在の関数またはフレームのソースコード全体をリストする)を忘れないでください。 なじみのないコードをステップスルーする場合や、コンテキスト全体の機能全体を表示する場合に非常に役立ちます。

例を示しましょう。

$ ./example3.py
>/code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
>/code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb)

コードの短いスニペットを表示するには、コマンド + l +(リスト)を使用します。 引数なしで、現在の行の周りに11行を出力するか、前のリストを継続します。 引数 。+`を渡すと、現在の行の前後に常に11行がリストされます: `+ l。

$ ./example3.py
>/code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10
 11
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb)

ブレークポイントを使用する

ブレークポイントは非常に便利で、時間を大幅に節約できます。 興味のない数十行をステップスルーする代わりに、調査したい場所にブレークポイントを作成するだけです。 必要に応じて、特定の条件が真の場合にのみ中断するようにpdbに指示することもできます。

コマンド + b +(ブレーク)を使用して、ブレークポイントを設定します。 実行を停止する行番号または関数名を指定できます。

breakの構文は次のとおりです。

b(reak) [ ([filename:]lineno | function) [, condition] ]

行番号 `+ lineno `の前に ` filename:+`が指定されていない場合、現在のソースファイルが使用されます。

`+ b `のオプションの2番目の引数である ` condition +`に注意してください。 これはとても強力です。 特定の条件が存在する場合にのみ中断したい状況を想像してください。 Pythonの式を2番目の引数として渡すと、式がtrueと評価されたときにpdbが壊れます。 以下の例でこれを行います。

この例では、ユーティリティモジュール `+ util.py `があります。 関数 ` get_path()+`で実行を停止するブレークポイントを設定しましょう。

メインスクリプト `+ example4.py +`のソースは次のとおりです。

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

ユーティリティモジュール `+ util.py +`のソースは次のとおりです。

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

まず、ソースファイル名と行番号を使用してブレークポイントを設定しましょう。

$ ./example4.py
>/code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at/code/util.py:5
(Pdb) c
>/code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb)

コマンド + c +(続行)は、ブレークポイントが見つかるまで実行を続けます。

次に、関数名を使用してブレークポイントを設定しましょう。

$ ./example4.py
>/code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at/code/util.py:1
(Pdb) c
>/code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb)

引数なしで `+ b +`を入力すると、すべてのブレークポイントのリストが表示されます。

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at/code/util.py:1
(Pdb)

コマンド `+ disable bpnumber `および ` enable bpnumber `を使用して、ブレークポイントを無効にしてから再度有効にすることができます。 「 bpnumber 」は、ブレークポイントリストの1列目の「 Num 」のブレークポイント番号です。 「 Enb +」列の値が変更されていることに注意してください。

(Pdb) disable 1
Disabled breakpoint 1 at/code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at/code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at/code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at/code/util.py:1
(Pdb)

ブレークポイントを削除するには、コマンド + cl +(クリア)を使用します。

cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]

次に、Python式を使用してブレークポイントを設定しましょう。 問題のある機能が特定の入力を受け取った場合にのみ中断したい状況を想像してください。

このシナリオ例では、相対パスを受け取ると、 `+ get_path()`関数は失敗します。 ファイルのパスが「/」で始まっていません。 この場合trueと評価される式を作成し、2番目の引数として ` b +`に渡します:

$ ./example4.py
>/code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at/code/util.py:1
(Pdb) c
>/code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './example4.py'
(Pdb)

上記のブレークポイントを作成し、実行を継続するために「+ c 」を入力すると、式の評価がtrueになるとpdbが停止します。 コマンド ` a +`(args)は、現在の関数の引数リストを出力します。

上記の例で、行番号ではなく関数名を使用してブレークポイントを設定する場合、式では関数の入力時に使用可能な関数引数またはグローバル変数のみを使用する必要があることに注意してください。 それ以外の場合、ブレークポイントは、式の値に関係なく、関数の実行を停止します。

関数内にある変数名を持つ式を使用してブレークする必要がある場合、つまり 関数の引数リストにない変数名、行番号を指定します:

$ ./example4.py
>/code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at/code/util.py:5
(Pdb) c
>/code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './example4.py'
(Pdb)

コマンド `+ tbreak `を使用して一時的なブレークポイントを設定することもできます。 最初にヒットすると自動的に削除されます。 ` b +`と同じ引数を使用します。

継続的な実行

これまで、「+ n 」(次)と「 s 」(ステップ)を使用してコードをステップ実行し、「 b 」(ブレーク)と「 c +」(継続)を使用してブレークポイントを使用してきました。

関連コマンドもあります: + unt +(まで)。

`+ c `のように実行を継続するには ` unt `を使用しますが、現在の行よりも大きい次の行で停止します。 時々、 ` unt +`がより便利で使いやすく、まさにあなたが望むものです。 これを以下の例で示します。

最初に、 `+ unt +`の構文と説明を見てみましょう。

Command Syntax Description

unt

unt(il) [lineno]

Without lineno, continue execution until the line with a number greater than the current one is reached. With lineno, continue execution until a line with a number greater or equal to that is reached. In both cases, also stop when the current frame returns.

行番号引数 `+ lineno `を渡すかどうかに応じて、 ` unt +`は2つの方法で動作します。

  • `+ lineno `がない場合、現在の行よりも大きい番号の行に到達するまで実行を続けます。 これは、「 n 」(次)に似ています。 これは、コードを実行して「ステップオーバー」する別の方法です。 「 n 」と「 unt 」の違いは、「 unt 」が現在の番号よりも大きい番号の行に到達したときにのみ停止することです。 ` n +`は次の論理的に実行された行で停止します。

  • + lineno +`を使用すると、それ以上の数の行に到達するまで実行を続けます。 これは、行番号引数を使用した `+ c +(継続)のようなものです。

どちらの場合も、現在のフレーム(関数)が戻ると、「+ n 」(次)および「 s 」(ステップ)と同様に、「 unt +」は停止します。

`+ unt +`で注意すべき主な動作は、現在の行または指定された行以上の行番号に達すると停止することです。

実行を継続し、現在のソースファイルでさらに停止する場合は、「+ unt 」を使用します。 行番号引数を渡すかどうかに応じて、「 n 」(次)と「 b +」(ブレーク)のハイブリッドのように扱うことができます。

以下の例には、ループのある関数があります。 ここでは、ループの各反復をステップ実行したり、ブレークポイントを設定したりせずに、コードの実行を継続し、ループの後に停止する必要があります。

`+ example4unt.py +`のサンプルソースは次のとおりです。

#!/usr/bin/env python3

import os


def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head


filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

そして、 `+ unt +`を使用したコンソール出力:

$ ./example4unt.py
>/code/example4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
>/code/example4unt.py(10)get_path()
-> for char in tail:
(Pdb)
>/code/example4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb)
>/code/example4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'example4unt.py')

最初に関数のソースを印刷するには「+ ll 」コマンドを使用し、次に「 unt 」を使用しました。 pdbは最後に入力されたコマンドを記憶しているため、[。keys]#Enter#を押して ` unt +`コマンドを繰り返します。 これは、現在の行よりも大きいソース行に到達するまで、コード全体で実行を継続しました。

上記のコンソール出力では、pdbが行 `+ 10 `と ` 11 `で一度だけ停止したことに注意してください。 ` unt `が使用されたため、ループの最初の反復でのみ実行が停止されました。 ただし、ループの各反復が実行されました。 これは、出力の最後の行で確認できます。 ` char `変数の値 ` 'y' `は、 ` tail `の値 ` 'example4unt.py' +`の最後の文字と同じです。

式を表示する

`+ p `と ` pp `で式を出力するのと同様に、コマンド ` display [expression] `を使用して、実行が停止したときに式の値が変更された場合、pdbに自動的に表示するように指示できます。 ` undisplay [expression] +`コマンドを使用して、表示式をクリアします。

両方のコマンドの構文と説明は次のとおりです。

Command Syntax Description

display

display [expression]

Display the value of expression if it changed, each time execution stops in the current frame. Without expression, list all display expressions for the current frame.

undisplay

undisplay [expression]

Do not display expression any more in the current frame. Without expression, clear all display expressions for the current frame.

以下は、ループでの使用を示す例 `+ example4display.py +`です。

$ ./example4display.py
>/code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at/code/example4display.py:11
(Pdb) c
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb)
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb)
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

上記の出力では、ブレークポイントがヒットするたびにその値が変更されたため、pdbは自動的に `+ char `変数の値を表示しました。 これが役立つ場合があり、まさにあなたが望むものですが、 ` display +`を使用する別の方法があります。

「+ display 」を複数回入力して、式の監視リストを作成できます。 これは、 ` p `よりも使いやすい場合があります。 興味のある式をすべて追加したら、単に「 display +」と入力して現在の値を表示します。

$ ./example4display.py
>/code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at/code/example4display.py:11
(Pdb) c
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './example4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'example4display.py'
(Pdb) c
>/code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './example4display.py'
head: '.'
tail: 'example4display.py'

Pythonの発信者ID

この最後のセクションでは、これまでに学んだことを基に、素晴らしい成果を上げます。 電話システムの発信者識別機能に関して「発信者ID」という名前を使用します。 Pythonに適用されることを除いて、これはまさにこの例が示すものです。

メインスクリプト `+ example5.py +`のソースは次のとおりです。

#!/usr/bin/env python3

import fileutil


def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path


filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

ユーティリティモジュール `+ fileutil.py +`は次のとおりです。

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

このシナリオでは、無効な入力で呼び出されるユーティリティモジュール `+ get_path()+`に関数を持つ大きなコードベースがあると想像してください。 ただし、異なるパッケージの多くの場所から呼び出されています。

発信者が誰であるかをどのように見つけますか?

コマンド + w +(where)を使用してスタックトレースを印刷し、最新のフレームを下部に表示します。

$ ./example5.py
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
 /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
 /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb)

紛らわしい場合や、スタックトレースやフレームが不明な場合でも心配しないでください。 これらの用語を以下に説明します。 聞こえるほど難しくはありません。

最新のフレームが一番下にあるので、そこから始めて、下から順に読んでください。 「-> +」で始まる行を確認しますが、関数「 get_path()」でpdbを入力するために「 pdb.set_trace()」が使用されているため、最初のインスタンスをスキップします。 この例では、関数 ` get_path()+`を呼び出したソース行は次のとおりです。

-> file_path = fileutil.get_path(full_fname)

各 `+→ +`の上の行には、ファイル名、行番号(括弧内)、およびソース行がある関数名が含まれています。 したがって、呼び出し元は次のとおりです。

 /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

デモンストレーション用のこの小さな例では驚くことではありませんが、悪い入力値の発生元を特定するための条件でブレークポイントを設定した大きなアプリケーションを想像してください。

これで、呼び出し元を見つける方法がわかりました。

しかし、このスタックトレースとフレームはどうですか?

スタックトレースは、関数呼び出しを追跡するためにPythonが作成したすべてのフレームの単なるリストです。 フレームは、Pythonが関数が呼び出されたときに作成され、戻るときに削除されるデータ構造です。 スタックは、任意の時点でのフレームまたは関数呼び出しの順序付きリストです。 (関数呼び出し)スタックは、関数が呼び出されてから戻るときに、アプリケーションのライフサイクル全体にわたって拡大および縮小します。

印刷されたとき、このフレームの順序付きリスト、スタックはスタックトレースと呼ばれます。 呼び出し元を見つけるために上で行ったように、コマンド `+ w +`を入力することでいつでも見ることができます。

_ 詳細については、https://en.wikipedia.org/wiki/Call_stack [Wikipediaの呼び出しスタックの記事]を参照してください。 _

pdbをよりよく理解し、さらに活用するために、 `+ w +`のヘルプをより詳しく見てみましょう。

(Pdb) h w
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands. 'bt' is an alias for this command.
  • pdbは「現在のフレーム」とはどういう意味ですか?*

現在のフレームは、pdbが実行を停止した現在の関数と考えてください。 つまり、現在のフレームはアプリケーションが現在一時停止している場所であり、 + p +(印刷)のようなpdbコマンドの参照の「フレーム」として使用されます。

`+ p `およびその他のコマンドは、必要に応じてコンテキストに現在のフレームを使用します。 ` p +`の場合、変数参照の検索と印刷に現在のフレームが使用されます。

pdbがスタックトレースを出力するとき、矢印「+> +」は現在のフレームを示します。

これはどのように便利ですか?

現在のフレームを変更するには、2つのコマンド「+ u 」(上)と「 d 」(下)を使用できます。 「 p +」と組み合わせると、任意のフレームのコールスタックに沿った任意の時点で、アプリケーションの変数と状態を検査できます。

両方のコマンドの構文と説明は次のとおりです。

Command Syntax Description

u

u(p) [count]

Move the current frame count (default one) levels up in the stack trace (to an older frame).

d

d(own) [count]

Move the current frame count (default one) levels down in the stack trace (to a newer frame).

「+ u 」コマンドと「 d 」コマンドを使用した例を見てみましょう。 このシナリオでは、 ` example5.py `の関数 ` get_file_info()`に対してローカルな変数 ` full_fname `を検査します。 これを行うには、コマンド「 u +」を使用して現在のフレームを1レベル上に変更する必要があります。

$ ./example5.py
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
 /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
 /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
>/code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./example5.py'
(Pdb) d
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./example5.py'
(Pdb)

`+ pdb.set_trace()`の呼び出しは関数 ` get_path()`の ` fileutil.py +`にあるため、現在のフレームは最初に設定されます。 上記の出力の1行目で確認できます。

>/code/fileutil.py(5)get_path()

`+ example5.py `の関数 ` get_file_info()`でローカル変数 ` full_fname `にアクセスして出力するには、コマンド ` u +`を使用して1レベル上に移動します。

(Pdb) u
>/code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

上記の「+ u 」の出力では、pdbが1行目の先頭に矢印「> 」を出力したことに注意してください。 これは、フレームが変更されたことを知らせるpdbであり、このソースの場所は現在のフレームです。 変数 ` full_fname `にアクセスできるようになりました。 また、2行目の「→ 」で始まるソース行が実行されたことを認識することが重要です。 フレームがスタックを上に移動したため、 ` fileutil.get_path()`が呼び出されました。 ` u `を使用して、スタックを(ある意味、時間を遡って)関数 ` example5.get_file_info()`に移動し、そこで ` fileutil.get_path()+`が呼び出されました。

例を続けると、「+ full_fname 」が印刷された後、「 d 」を使用して現在のフレームが元の場所に移動され、「 get_path()」のローカル変数「 fname +」が印刷されました。

必要であれば、 `+ count `引数を ` u `または ` d `に渡すことで、一度に複数のフレームを移動できます。 たとえば、「 u 2+」と入力して、「+ example5.py +」のモジュールレベルに移動できます。

$ ./example5.py
>/code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
>/code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./example5.py'
(Pdb)

デバッグしてさまざまなことを考えているときに、自分がどこにいるかを忘れがちです。 実行が一時停止されている場所と現在のフレームが何であるかを確認するために、適切な名前のコマンド「+ w +」(where)をいつでも使用できることを覚えておいてください。

必須のpdbコマンド

pdbで少し時間を費やすと、少しの知識が大いに役立つことがわかります。 ヘルプは常に `+ h +`コマンドで利用できます。

「+ h 」または「 help <topic> +」と入力するだけで、すべてのコマンドのリストを取得したり、特定のコマンドまたはトピックのヘルプを取得したりできます。

クイックリファレンスとして、重要なコマンドのリストを次に示します。

Command Description

p

Print the value of an expression.

pp

Pretty-print the value of an expression.

n

Continue execution until the next line in the current function is reached or it returns.

s

Execute the current line and stop at the first possible occasion (either in a function that is called or in the current function).

c

Continue execution and only stop when a breakpoint is encountered.

unt

Continue execution until the line with a number greater than the current one is reached. With a line number argument, continue execution until a line with a number greater or equal to that is reached.

l

List source code for the current file. Without arguments, list 11 lines around the current line or continue the previous listing.

ll

List the whole source code for the current function or frame.

b

With no arguments, list all breaks. With a line number argument, set a breakpoint at this line in the current file.

w

Print a stack trace, with the most recent frame at the bottom. An arrow indicates the current frame, which determines the context of most commands.

u

Move the current frame count (default one) levels up in the stack trace (to an older frame).

d

Move the current frame count (default one) levels down in the stack trace (to a newer frame).

h

See a list of available commands.

h <topic>

Show help for a command or topic.

h pdb

Show the full pdb documentation.

q

Quit the debugger and exit.

pdbを使用したPythonデバッグ:結論

このチュートリアルでは、pdbのいくつかの基本的で一般的な使用法について説明しました。

  • 印刷式

  • + n +(次)および + s +(ステップ)でコードをステップ実行する

  • ブレークポイントを使用する

  • `+ unt +`で実行を続ける(まで)

  • 式を表示する

  • 関数の呼び出し元を見つける

お役に立てば幸いです。 詳細については、次をご覧ください。

例で使用されているソースコードは、関連するhttps://github.com/natej/pdb-basics[GitHubリポジトリ]にあります。 デバッグ中にチートシートとして使用できる、印刷可能なpdbコマンドリファレンスを確認してください。

無料ボーナス: link:#[ここをクリックして印刷可能な「pdbコマンドリファレンス」(PDF)を入手してください。これは机上に置いてデバッグ中に参照できます。

また、GUIベースのPythonデバッガーを試してみたい場合は、https://realpython.com/python-ides-code-editors-guide/[Python IDEs and Editors Guide]を読んで、どのオプションが最適かを確認してください。あなたのために。 ハッピーパイソン!