Jupyter Notebook のコード実行履歴を Python スクリプトとして保存する方法

今回は、 Jupyter Notebook (JupyterLab) のコード実行履歴を Python スクリプトとして保存する方法について紹介します。
先に %history マジックコマンド等の一般的な方法の紹介を行い、最後に自作した関数を紹介します。
    1. %history マジックコマンド
- 
ノートブックのセル内で以下を実行すると、コードの実行履歴が出力されます。 %history
- 
主なオプション オプション 説明 -n行番号を表示する。 -o出力結果も表示する。(※後述の通り、表示されない出力があるので注意。) -p実行コマンドの前に >>>をつける (-oを使う際に見やすくなる)-tマジックコマンドやエイリアスを、Pythonのコードに翻訳してから表示する。 -f <FILENAME>ファイルに結果を保存する。 
# In [1] --------------------
a = 2
b = 3
# In [2] --------------------
a + b
# In [3] --------------------
display(a * b)
# In [4] --------------------
print(a - b)
# In [5] --------------------
%history- 
実行結果 a = 2 b = 3 a + b display(a * b) print(a - b) %history
- 
-nで行番号表示。1: a = 2 b = 3 2: a + b 3: display(a * b) 4: print(a - b) 5: %history -n
- 
-oで実行結果をあわせて表示。
 (※printやdisplayの出力結果は表示されない。)a = 2 b = 3 a + b 5 display(a * b) print(a - b) %history -o
- 
-tでマジックコマンド等をPythonのコードに変換してから表示。a = 2 b = 3 a + b display(a * b) print(a - b) get_ipython().run_line_magic('history', '-t')
2. ipynb ファイルを直接 Python スクリプトに変換する
実行履歴とは若干異なりますが、以下の方法を用いるとノートブックを直接 Python スクリプトに変換することができます。
- 
Jupyter Notebook の File -> Download as -> Python (.py) で変換する。 
- 
以下のコマンドでも変換できる。 jupyter nbconvert --to python <ipynbファイル>
3. コード実行履歴を取得する関数を自作する
上記2つの方法では自分が意図する出力を行えなかったため%historyコマンドのコードを参考にして自作した関数を紹介します。
機械学習の実験管理を行っていた際に、ノートブックで実行したコードを実験結果と一緒に保存しておきたい場面がありました。
実験では、ローカルにある ipynb ファイルを VSCode でリモートサーバに接続して実行していたため、
リモート側で完結した処理にしようとすると、2番目の「ipynb ファイルを直接 Python スクリプトに変換する」は不可。
また、 %history コマンドでは以下の要望すべてを満たす出力ができないため、専用の関数を自作することにしました。
- セルごとに分けて出力したい。
- 出力結果をPythonでそのまま実行できるようにしたい。
from IPython import get_ipython
def get_history_simple(start=0, stop=None):
    """Jupyter Notebook のコード実行履歴を取得する.
    Args:
        start (int, optional): 開始セル番号. Defaults to 0.
        stop (int or None, optional): 終了セル番号. Defaults to None.
    Returns:
        str: コード実行履歴
    """
    cell_num = get_ipython().execution_count
    if start < 0:
        start = cell_num + start
    if stop is not None and stop < 0:
        stop = cell_num + stop
    if stop is not None:
        stop += 1
    hist = get_ipython().history_manager.get_range(
        raw=True, output=False, start=start, stop=stop
    )
    codes = []
    for _, lineno, inline in hist:
        if inline.strip() == "":
            continue
        codes.append(f'# In [{lineno}] ' + '-' * 20)
        codes.append(f'{inline}\n')
    return "\n".join(codes)実行すると、以下のような出力となります。
# In [1] --------------------
from jupyter_utils import get_history_simple
# In [2] --------------------
a = 2
b = 3
# In [3] --------------------
a + b
# In [4] --------------------
display(a * b)
# In [5] --------------------
print(a - b)
# In [6] --------------------
get_history_simple()簡単な解説
- get_ipython().execution_countで現在のセル番号を取得します。
- start,- stopにマイナスの値を入れられるように調整しています。
- get_ipython().history_manager.get_rangeを使用してコードの実行履歴を取得しています。
実際に使用している関数では以下のような機能も追加しています。
(あまり綺麗なコードではないため今回は省略しています。)
- マジックコマンドをコメントアウトする機能
 (簡易的な文字列チェックでマジックコマンドを判定しコメントアウトしてます。)
- 出力内容をprintやdisplayで出力される文字列も含めてコメントとして表示させる機能
 (%%captureコマンドのコードを参考に出力のキャプチャを行っています。)

