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を使いたかった

