Visual Studio Codeを使って、ローカルのJupyter ServerにアタッチしてPythonモジュール(.pyファイル)をデバッグする方法を説明します。前半はVS Codeからリモートサーバー上のJupyterLabにアタッチしてデバッグを行う手順を参考にさせていただきました。
1. アタッチって?
Visual Studio Codeのデバッグには2種類があります:
- 起動(Launch): デバッガーがプロセスを起動し、その上でデバッグを行う
- 接続(Attach): 既に実行しているプロセスにアタッチし、その上でデバッグを行う
今回は先にJupyter Notebookを開き、そのサーバーにアタッチしてデバッグしますので、後者になります。アタッチによるデバッグの詳細はこちらやこちらで丁寧に解説されています。
2. 手動でアタッチ
2.1. フォルダー構成
project_root
├ .vscode
│ └launch.json
├ mymodule.py
└ notebook.ipynb
%autoreload 2
はモジュールの更新を自動で取り込むためのマジックコマンドです。詳しくはこちらを参照。モジュールの修正で試行錯誤するとき入れておくと、修正が直ちに反映されるので便利です。
#%%
%load_ext autoreload
%autoreload 2
import os
os.getpid()
#%%
import mymodule
mymodule.double_sum(1,2)
def double_sum(x, y):
z = (x + y) * 2
return z
launch.jsonは、Ctrl+Shift+D
-> launch.json ファイルを作成します
をクリック -> Python
-> Attach using Process ID
で自動で作成されます:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Attach using Process Id",
"type": "python",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
2.2. デバッグの実行
notebook.ipynb
ファイルとmymodule.py
ファイルをVSCodeで開きます。ノートブックの1つめのセルを実行するとPIDが表示されます。
[F5]でデバッグを開始します。"Select the process to attach to"と表示されますので先ほどのPIDを入力しましょう。
一番下のステータス バーの色が青からオレンジに変わり、mymodule.py
ファイル側にコネクター(赤い丸で囲ったとこ)みたいなアイコンを含むデバッグ ツールバーが表示されます。3行目にブレークポイントを設定してみましょう。
Jupyterノートブックに戻り、2つめのセルを実行すれば、先ほどのブレークポイントで止まります。
3. 自動でアタッチ
ここまでだとつまらないので、もう一つネタを追加します。
正直、PIDを毎回入力するのは面倒ですね。以下で、この作業を自動化していきます。Windows限定ですが、ちょちょいと修正すればLinuxやmacOSでも動かせると思います。
3.1. 拡張機能Tasks Shell Inputのインストール
launch.json
内でコマンドの結果を利用できるようにするため、VSCode拡張機能 - Tasks Shell Inputをインストールします。
3.2. ファイルの修正
notebook.ipynb
ファイルは、1つめのセルを修正し、PIDを%LOCALAPPDATA%\Temp\pid.txt
(=C:\Users\ユーザー名\AppData\Local\Temp\pid.txt
)に出力させるようにします。
#%%
%load_ext autoreload
%autoreload 2
import os
with open(f'{os.environ["LOCALAPPDATA"]}\\Temp\\pid.txt', mode='w') as f:
f.write(f'{os.getpid()}')
#%%
import mymodule
mymodule.double_sum(1,2)
launch.json
ファイルは、configurations
に"Python: Auto Attach"
を追加します。先ほどとの違いは、PIDをcommandタイプのinput変数readPID
から取得している点です。readPID
は、拡張機能Tasks Shell Input("shellCommand.execute"
の部分)を使って、コマンドプロンプトのtype
コマンドでファイルからPIDを取得します。
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Attach using Process Id",
"type": "python",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Python: Auto Attach",
"type": "python",
"request": "attach",
"processId": "${input:readPID}"
}
],
"inputs": [
{
"id": "readPID",
"type": "command",
"command": "shellCommand.execute",
"args": {
"command": "type ${env:LOCALAPPDATA}\\Temp\\pid.txt",
"useFirstResult": "True",
"fieldSeparator": " ",
}
}
]
}
3.3. デバッグの実行
先ほどと同じくまずは1つめのセルを実行します。そして、Ctrl+Shit+P
-> Debug: 選択してデバッグを開始
-> Python: Auto Attach
でデバッガーを起動します。今度はPIDを入力せずにアタッチすることができました。
4. もっと自動でアタッチ
PIDをnotebook.ipynb
の1つめのセルで出力していましたが、ipythonのstartup機能(詳しくはこちら)を使えばその部分をnotebook.ipynb
から除くことができます。
%USERPROFILE%\.ipython\profile_default\startup
フォルダー(=C:\USERS\ユーザー名\.ipython\profile_default\startup
フォルダー)に、以下の内容の00-vscode_attach_PID.ipy
ファイルを作成します。このフォルダーは特殊なフォルダーで、Notebook起動時にこのフォルダー内のスクリプト(.pyファイル)が自動的に実行されます(公式またはこちらを参照)。
%load_ext autoreload
%autoreload 2
import os
from pathlib import Path
pid_txt = Path(f'{os.environ["LOCALAPPDATA"]}\\Temp\\pid.txt')
next_pid_txt = Path(f'{os.environ["LOCALAPPDATA"]}\\Temp\\next_pid.txt')
if next_pid_txt.exists():
next_pid_txt.replace(pid_txt)
with open(next_pid_txt, mode='w') as f:
f.write(f'{os.getpid()}')
PIDをファイル出力しているだけですが、なぜかVisual Studio Code上のJupyter Notebookはひとつ前のタイミングで出力したPIDになる(何を言ってるのか わからねーと思うが)ので、変なことをしています。(JupyterLabで.ipynb
ファイルを開く場合には、前と同じコードで大丈夫でした。)
あとは同じです。notebook.ipynb
の1つめのセルが無くても、デバッグ&アタッチが可能になりました。
5. やり残し
以下が気になるけどもういいや('A`)
- 本当は拡張機能Tasks Shell Inputを使わずに素のvscodeだけでやりたかった
- コマンドプロンプトじゃなくてPowerShellを使いたかった