はじめに
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 のカーネルはパーサーにかけた後のコードしか保持していないらしいのでボツ案となった。