Jupyter対話環境を使ってVS Codeでノートブックのデバッグ:Visual Studio Codeで快適Pythonライフ(2/2 ページ)
Jupyter拡張機能が提供する対話環境の基本的な使い方と、それを利用したノートブックのデバッグの方法を紹介します。
ノートブックのデバッグ
ここでは、以下のコードだけを含んだノートブックをデバッグしてみましょう。
def fact(num):
if num == 0:
return 1
else:
return num * fact(num - 1)
for num in range(5):
num_list = [str(n) for n in range(1, num+1)]
msg = ' * '.join(num_list)
msg = f'{num}! = ' + msg + ' ='
print(msg, fact(num))
2つのブロックを別々のセルに記述することも考えましたが、ここでは1つのセルにまとめてあります。
コードについて簡単にまとめておくと、fact関数は階乗を求めるだけです。その下のfor文では0〜4の各値について、「X! = …… =」という文字列を組み立てて、その文字列とともにfact関数の呼び出し結果を表示しています(Xには0〜4の各値が代入されます)。
最初の行の内包表記は、['1', '2', ……]のような文字列リストを生成することを目的としています。そして、「' * '」を区切り文字としてそれらを連結し、最後に「X! =」と連結することで、「2! = 1 * 2 =」などの文字列にしようというわけです。これに続けて、fact関数呼び出しの結果を出力することで、「2! = 1 * 2 = 2」のような出力が完成します。
ちなみにセルを実行すると、次のような結果が得られます。
2の階乗以降はキレイな出力といえますが、0の階乗と1の階乗はちょっとうまくないですね。そこで、デバッグをして、コードを直してみましょう。
とはいえ、実はJupyter Notebookのノートブック(.ipynbファイル)をそのままデバッグすることはできません。そのためには、ノートブックを一度、Pythonスクリプト(.pyファイル)に変換する必要があります(先ほどのJupyter対話環境での変換とは異なり、JupyterノートブックのPythonスクリプトへの変換ではインデントがちゃんと維持されるので、後から手を加える必要はありません)。
変換するには、エディタ領域でノートブックをアクティブにして、コマンドパレットから[Jupyter: Export to Python Script]コマンドを実行します(あるいは、ノートブックの上部にある[…]をクリックして[Export]を選択し、エクスポート先として[Python Script]を選択してもよいでしょう)。
これにより、以下のようにノートブックがPythonのスクリプトへと変換されて、それがエディタ領域に表示されます(ここでは「mynotebook.py」という名前で保存してあります)。
簡単に説明をしておくと、セルは「# %%」で区切られます。上の例では1つのセルに全てのコードを記述していたので、セルは1つだけです。3行目にある「# %%」行がそのセルの始まりで、次のセルがないので、セルの範囲はファイル末尾までとなっています(セルの範囲が青い線で示されていますね)。for文の前に「# %%」行を追加すると、セルが分割されます。試しに入れてみたものを以下に示します。
セルの上部にあるリンクに注目してください。これらのリンクには次のような種類があります。
- [Run Cell]:このセルを実行する
- [Run Bellow]:このセル以降を実行する
- [Run Above]:このセルの上にあるセルを実行する
- [Debug Cell]:セルをPython対話環境でデバッグする
- [Go to [x]]:Jupyter対話環境でそのコードを実行した箇所を表示する
[Run Cell]リンクはJupyter対話環境を使ってそのセルの内容を実行します。このときには実行された順に、1、2、……という番号が対話環境の出力に振られます。最後の[Go to [x]]は、対話環境で番号xの出力に移動するのに使うものです。[Run Bellow]リンクはそれをクリックしたセルとそれ以降のセルをまとめて実行するのに使います。[Run Above]リンクは、そのセルの直前までのセルを実行します。[Debug Cell]リンクは、そのセルのコードをデバッグします。
例として、最初のセルにある[Run Bellow]リンクをクリックしたところを以下に示します(ここでは上で追加した「# %%」行がある、つまり、2つのセルがあるものとします)。
ご覧の通り、Pythonスクリプトの隣に、前ページで紹介したJupyter対話環境が開かれて、そこでコードが実行されています。コードが全て表示されていないのは、セルが折りたたまれているからです(対話環境の上部にある[…]ボタンをクリックして[Expand]選択すると、全てのセルが展開されます。あるいは、セルの左隣にあるアイコンをクリックすると、対応するセルだけが展開されます)。
セルをデバッグするには、今述べた[Debug Cell]リンクをクリックするのも一つの手ではありますが、実際には変換後の(「# %%」で区切られたセルを含む)Pythonスクリプトをエディタ領域でアクティブにして、コマンドパレットから[Jupyter: Debug Current File in Interactive Window]コマンドを実行するのがよいでしょう([Debug Cell]リンクでは、実行しているコードとハイライト表示される行が対応していないときがあるようです)。
このコマンドを実行すると、先ほどと同じように、Jupyter対話環境が開かれて(既にJupyter対話環境が開かれているときにはそれが流用されます)、次のような画面になります。以下の画像では、サイドバーに[実行とデバッグ]ビューを表示しています。また、Pythonスクリプトが表示されたタブでは、先頭行で実行が停止した状態になっています。
VS Codeのタイトルバーの下にはデバッグ実行で使用するツールバーが表示されています。これらを使っても構いませんが、Pythonスクリプトの方には[Run Cell]リンクなどがあった箇所に、[Continue]/[Stop]/[Step over]の3つのリンクが表示されていることにも注目してください。ここでは、後者の3つのリンクを使ってみます。
[Continue]リンクはツールバー左端にある[続行]ボタンと同じです。つまり、ブレークポイントに当たるか、コードの最後に到着するまで実行を続けます。[Stop]ボタンはツールバー右端にある[切断/停止]ボタンと同じです。つまり、デバッグを終了します。[Step over]リンクはツールバーの[ステップオーバー]ボタンと同じです。つまり、その行を実行して、次の行を実行する前に停止します。
ここでは、[Step over]リンクで実行を続けていくことにします。カーソルがある行はdef文なので関数を定義して、次のfor文へとカーソルが移動します。
ここでさらに[Step over]リンクを3回クリックすると、コードは以下の箇所で停止します。
ここで[実行とデバッグ]ビューの[変数]セクションに注目してみましょう。変数msgの値は空文字列('')に、変数numの値が「0」になっています。ここでfor文の直下にあるリスト内包表記では「range(1, num+1)」としていることに注意してください。つまり、このときにはrange関数の戻り値である「range(1, 1)」は空の範囲を示しています。その結果、このリスト内包表記により、空のリストが作成され、「msg = ' * '.join(num_list)」行では変数msgには空文字列が代入されたというわけです。ここまでくれば、現在カーソルがある「msg = f'{num}! = ' + msg + ' ='」行では右辺にある変数msgが空文字列なので、左辺の変数msgに「0! = =」が代入されることになります。
なお、次のループでは変数numの値が「1」となりますが、このときには「range(1, 2)」を含んだリスト内包表記により「['1']」という単一要素のリストが作られます。そして、「msg = ' * '.join(num_list)」行により変数msgの値は「'1'」に、これを受けて「msg = f'{num}! = ' + msg + ' ='」行で変数msgの値は「1! = 1 =」となります。最終的にfact関数呼び出しの結果と合わせて、「1! = 1 = 1」と表示されるのですが、真ん中の部分は不要といえば不要です。Jupyter対話環境での出力を以下に示します。
というわけで、このコードを修正するにはどうすればよいかというと、「0!」と「1!」のときには「0! = 1」「1! = 1」と表示されるようにするのがよさそうです。そのためには、変数numの値に応じて、変数msgの値を切り分けるように「msg = f'{num}! =' if num < 2 else f'{num}! = ' + msg + ' ='」というコードを書けばよいでしょう。ここで、[Stop]リンクをクリックして、デバッグを中断して、コードを以下のように修正します。
# %%
for num in range(5):
num_list = [str(n) for n in range(1, num+1)]
msg = ' * '.join(num_list)
msg = f'{num}! =' if num < 2 else f'{num}! = ' + msg + ' ='
print(msg, fact(num))
修正後の実行結果を以下に示します。
修正後のPythonスクリプトは、コマンドパレットから[Jupyter: Export Current Python File as Jupyter Notebook]コマンドでノートブックとして保存できます。
元ファイルを上書きすれば、デバッグによって修正されたコードを元のファイルに反映させることも可能です。
少し駆け足となりましたが、VS Codeではこんな感じでJupyter Notebookのデバッグも行えます。次回はもう少しJupyter拡張機能について見てみる予定です。
Copyright© Digital Advantage Corp. All Rights Reserved.