これはなに
VS CodeにPythonの拡張機能 を入れると、VS CodeでPythonコードをデバッグできるようになる。 ただ、この機能をきちんと使ったことがないため、すこし使ってみた。 その際やったことを記録しておく。
環境 | 前提条件
- Visual Studio Code
- Pythonの拡張機能 をインストール済み
- Python 3.11.1
対象とするPythonコード
次のPythonコードをデバッグの対象とする。
|
|
リストに値を追加するだけの関数である。 追加用のリストが指定されない場合は、空のリストに与えられた値を追加したい。 すなわち、理想の出力は下記である。
$ python src/main.py
[1]
[2]
[3]
しかし、これをそのまま実行すると下記の結果が得られる。
$ python src/main.py
[1]
[1, 2]
[1, 2, 3]
おかしいなあ…。なんでぇ…?
デバッグする
何が想定通りに動いていないのかを見る。
ブレークポイントを打つ
最初に、詳細を確認したいところにブレークポイントというものを打つ。 VS Codeでブレークポイントを打つには、打ちたい行の左側をクリックするか、その行にカーソルを合わせてF9を押す。
今回は、add_to_list()
の結果をprint
するところに打ってみる。
デバッグモードで実行する
VS CodeにPythonの拡張機能 を入れていれば、エディタの左タブにある「デバッグして実行(Run and Debug)」タブが使える。Ctrl + Shift + Dでも開ける。
このタブの「デバッグして実行(Run and Debug)」ボタンをクリックすると、デバッグ構成を指定するよういわれる。 今回は「Pythonファイル」を選ぶ。
すると、Pythonコードが最初のブレークポイントの手前まで実行される。
コードをデバッグする
デバッグモードで実行した時点では、まだprint(add_to_list(1))
が実行されていない。そのため、関数add_to_list
の変数などは定義されておらず、何も表示されない。
この状態になったら、F11を押すか、下図のツールボックスの「ステップイン(Step Into)」をクリックする。
「ステップイン」は、その行が関数の場合、関数の中に入り、その関数の1行目の実行前まで進む。 その行が関数でない場合は、1行だけ実行し次の行へ進む。
今回はadd_to_list
関数でステップインを実行するため、add_to_list
の中に入り、その関数の1行目の実行前まで進むことになる。
すなわち、i
とi_list
が定義された状態に移行する。
左側のVARIABLES
で、i
が1
、i_list
が[]
であると確認できる。
これは想定通りの動作である。
次に、この状態で「ステップオーバー」を実行する。 「ステップオーバー」を実行するには、F10を押すか、下図のツールボックスの「ステップオーバー(Step Over)」をクリックする。
「ステップオーバー」を実行すると、その行が関数か否かにかかわらず1行を実行し、次の行の実行前まで進む。
今回の場合はi_list.append(i)
だけが実行される。
左側のVARIABLES
で、i_list
が[]
から[1]
に変化した。
i
がi_list
にきちんと追加されている。
最初の呼び出しではadd_to_list
がきちんと動作しているとわかったため、次の呼び出しに進む。
次のブレークポイントまで進むには「続行」を実行する。「続行」を実行すると、現在の行から次のブレークポイントまでのコードを実行し、次のブレークポイントの前まで進める。
「続行」するには、F5を押すか、「続行(Continue)」をクリックする。
これで、次のブレークポイント、すなわち2回目のadd_to_list
呼び出し前まで進む。
2回目のadd_to_list
呼び出し前まで進んだので、「ステップイン」して呼び出しの中身を見てみる。F11を押すか、ツールボックスの「ステップイン(Step Into)」をクリックする。
左側のVARIABLES
を見る。すると、2回目のadd_to_list
呼び出しでは、初期値のi
は2
だが、i_list
は空ではなく[1]
になってしまっているとわかる。
これは想定動作と異なる。想定では、i_list
を引数で指定していない場合は空のリストになってほしいのに、そうなっていないのだ。
ちなみに、この状態でステップオーバーすると、i_list
に2
が追加され、i_list
は[1, 2]
となる。
すなわち、関数add_to_list
が想定通りの動作をしなかったのは、少なくとも2回目のadd_to_list
の呼び出しの時点で、i_list
が空のリストに初期化されていないからだとわかる。
コードを修正する
i_list
が空のリストにきちんと初期化されていないのが原因であるから、きちんと初期化されるようにすればよい。
|
|
これで想定通りの結果が得られる。
$ python src/main.py
[1]
[2]
[3]
やったね!
おわりに
あくまで使ってみた機能は少しだけである。WATCHとかCALL STACKとかは使ってないのでよくわからない。
ただ、今回使ってみた機能だけでもわざわざprint
しなくてよくなるため、デバッグ効率が上がりそう。
ちなみに
今回のデバッグ対象コードの元ネタはこれ。
Python、値がリファレンスであるのと、デフォルト実引数が一度しか評価されないという仕様があいまって最悪の落とし穴が生まれている。https://t.co/s7OqNfVxPw
— 江添亮 (@EzoeRyou) May 25, 2022
この仕様を初めて知ったときは恐怖を覚えた。