はじめに
Jupyter notebook でデータ分析なり実験なりしていると、分析用のクラス等を定義した他の notebook の特定のセルをインポートしたいなんてことがよくあります。うまくいきそうな方法を2つほど紹介します。
1. %%writefile マジックコマンド
一番手軽な方法はマジックコマンドの%%writefileを使うことです。セルの先頭に%%writefile filenameとすることでfilenameにセルの内容がコピーされます。あとは他の notebook からfilenameをインポートするだけです。
%%writefile hoge.py
class Hoge:
def __init__(self):
print('Hello from Hoge')
import hoge
x = hoge.Hoge()
2. 頑張って自作する
公式が「それみんなやりたがるし、public な API だけでそんな苦労しなくてもできるよ!」って言ってるだったらコマンドひとつでできるようにしておいてほしいのでリンク先のコードを参考に自作します1。
リンク先のコードはモジュールとして読み込んだ notebook のコードセルをすべて実行してしまうので、コードセルの先頭にあるコメントに# modcellと書いてあるセルを識別して読み込むように改造してみました。
modcellというライブラリとして公開したのでもしよければ使ってください。
- GitHub: wsuzume/modcell
(探せば私よりも clever なライブラリ作ってる人がいるはずなので、もしいいライブラリを他にご存知の方がいらっしゃいましたら教えていただけると幸いです)
Usage
modcellはsys.pathに登録されているディレクトリに存在する拡張子.ipynbのファイルを探索の対象とし、コードセルの(マジックコマンドを除いた)先頭の行に書かれている# modcellというコメントを識別してモジュールとしてインポートします。
インストール
pip install modcell
modcell のインポート
modcellモジュールをインポートすると.ipynbを読み込むためのローダーがsys.meta_pathに追加されます。以降は拡張子が.ipynbのファイルをインポートすることが可能になります。
import modcell as mods
.ipynb ファイルをインポート
通常のモジュールと同じように拡張子を省いた名前でインポートすると、(IPythonのマジックコマンドを除いた)セルの先頭に# modcellと書かれたセルをすべてインポートします。
# modcell
class TestModule:
def __init__(self):
pass
def hello(self):
print('Hello from TestModule')
import test_module as mod
x = mod.TestModule()
x.hello()
modcellは IPython のマジックコマンド(!または%で始まる行)を無視します。つまりmodcellによるインポートが行われたとき%autoreloadや%%timeは実行されません。
またmodcell自体が%autoreloadとかに対応してるかはテストしてないので知りません。Restart and Run allすれば他の notebook を再読み込みしてくれることは確認しております。
コンパイル
%%writefileの上位互換です。インポートしたすべてのセルをファイルに出力します。
import modcell as mods
import test_module
with open('module.py', mode='w') as f:
mods.compile(out=f, source_info=True)
以下のようにフォーマットされます。
# test_module.ipynb ---------
# ---
class TestModule:
def __init__(self):
pass
def hello(self):
print('Hello from TestModule')
# ---
# --------- test_module.ipynb
デバッグなどの観点からセルに対応した区切りコメントが出力されますが、邪魔なときはsource_info=Falseを指定すれば出力されません。
.ipynb から特定のタグを持つセルだけをインポート
セルの先頭に書くコメントを
# modcell: tagname
class TestModule:
def __init__(self):
pass
def hello(self):
print('Hello from TestModule')
の構文にすることで各セルにひとつだけタグをつけることができます。インポートする場合は
import modcell as mods
mod = mods._import('test_module', tag='tagname')
x = mod.TestModule().hello()
とすることでtagnameというタグがついたセルだけを読み込むことができます。
複数のモジュールファイルへのコンパイル
modcellはデフォルトでmodcell.ModCellクラスのインスタンスをひとつ生成しており、すべての関数はそのデフォルトインスタンスのメソッドとして実行されています。このデフォルトインスタンスはmodcell.default()関数で取得することができますが、ユーザが操作することは非推奨です。
ユーザは必要に応じて複数のModCellインスタンスを作成することが可能です。
import modcell as mods
mod_1 = mods.ModCell()
mod_2 = mods.ModCell()
...
つまり以下のようなコードを実行することで、複数の.ipynbから複数の.pyファイルを生成できます。
import modcell as mods
mod_debug = mods.ModCell()
nb1_debug = mod_debug._import('notebook_1', tag='debug')
nb2 = mod_debug._import('notebook_2')
nb3 = mod_debug._import('notebook_3')
with open('../module/mod_debug.py', mode='w') as f:
mod_debug.compile(out=f)
mod_test = mods.ModCell()
nb1_test = mod_test._import('notebook_1', tag='test')
nb4 = mod_test._import('notebook_4')
with open('../module/mod_test.py', mode='w') as f:
mod_test.compile(out=f)
おまけ
もうひとつ思いついた方法として「IPython のカーネルにアタッチして他の notebook のコードセルの中身を引っ張ってくる」というものがあったが、どうもコードセルの中身を保持しているのはフロントエンドの JavaScript であって IPython のカーネルはパーサーにかけた後のコードしか保持していないらしいのでボツ案となった。