launch_new_instance = IPKernelApp.launch_instance
動機
jupyter notebook上でのセル実行がどうなっているのか知りたくなった
状況把握
code:python
# execute on colaboratory
print(sys.executable) # => /usr/bin/python3
print(' '.join(sys.argv)) # => /usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py -f /root/.local/share/jupyter/runtime/kernel-434ca84d-5a20-46ab-a942-5440c754c2af.json
Colaboratory上で実行環境をみたところ、ipykernel_launcher.py を実行していてkernel-uuid.json を読み込んでカーネルに接続しているっぽいことが読み取れる
ファイルの中身を見てみる
cat /root/.local/share/jupyter/runtime/kernel-434ca84d-5a20-46ab-a942-5440c754c2af.json
code: json
{
"shell_port": 45705,
"iopub_port": 60539,
"stdin_port": 56627,
"control_port": 34907,
"hb_port": 52625,
"ip": "127.0.0.1",
"key": "",
"transport": "tcp",
"signature_scheme": "hmac-sha256",
"kernel_name": ""
}
cat /usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py
code:pyhton
"""Entry point for launching an IPython kernel.
This is separate from the ipykernel package so we can avoid doing imports until
after removing the cwd from sys.path.
"""
import sys
if __name__ == '__main__':
# Remove the CWD from sys.path while we load stuff.
# This is added back by InteractiveShellApp.init_path()
from ipykernel import kernelapp as app
app.launch_new_instance()
"読み込み中にsys.pathからCWDを削除します。これは InteractiveShellApp.init_path()によって追加されます" とのことだが、それぞれなんのことを指しているのか?
起動時に初期化された後、リストの先頭 (path[0]) には Python インタプリタを起動したスクリプトのあるディレクトリが挿入されます。スクリプトのディレクトリがない (インタプリタが対話セッションで起動された時や、スクリプトを標準入力から読み込んだ場合など) 場合、 path[0] は空文字列となり、Python はカレントディレクトリからモジュールの検索を開始します。
del sys.path[0] していることからもわかるように、カレントディレクトリでの検索を避ける意図があるように見える
code: python
def init_path(self):
"""Add current working directory, '', to sys.path
Unlike Python's default, we insert before the first site-packages
or dist-packages directory,
so that it is after the standard library.
.. versionchanged:: 7.2
Try to insert after the standard library, instead of first.
"""
if '' in sys.path:
return
for idx, path in enumerate(sys.path):
parent, last_part = os.path.split(path)
if last_part in {'site-packages', 'dist-packages'}:
break
else:
# no site-packages or dist-packages found (?!)
# back to original behavior of inserting at the front
idx = 0
sys.path.insert(idx, '')
なんとなくの理解としては、InteractiveShellを実行するときにsys.pathの先頭に空文字列が挿入されるから、重複を避けるために予め除去しておくということかしら(?!)
で、最後にfrom ipykernel import kernelapp as app でインポートしているわけだけども、次の行がまったくわからん!?どうなってるんだ????
理解を妨げている原因の特定
app.launch_new_instance()を理解すべく、インポート先のソース(iPython/ipykernel/ipykernel/kernelapp.py)を見てみる。
しかし見れば見るほどわからない……、この部分 ってどうなってるの???????? ↓
code: python
# iPython/ipykernel/ipykernel/kernelapp.py
launch_new_instance = IPKernelApp.launch_instance # いや意味わからんここ
def main():
"""Run an IPKernel as an application"""
app = IPKernelApp.instance()
app.initialize()
app.start()
if __name__ == '__main__':
main()
app.launch_new_instance() のように使っているので、kernelapp.py 内のクラスIPKernelAppのメソッドとして定義されてるかと思えばどこにも見当たらない
怪しいのは継承元のクラスであろうと考え、ソースコードを遡ってみる
class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMixin)と定義されているから、候補となるのはこの3つのクラスである
まずBaseIPythonApplication はfrom IPython.core.application import BaseIPythonApplication, base_flags, base_aliases, catch_config_errorとしてインポートされているから、参照すべきはこの部分 であることがわかった このクラスにもまたlaunch_new_instance() が見つからないので、さらに継承元クラスである Application を探る
当該インポート文をみるとfrom traitlets.config.application import Application, catch_config_error となっており、このクラス まで遡ればいいことがわかる ここまできてやっと、この部分 にクラスメソッドとしてlaunch_instance(cls, argv=None, **kwargs) が存在していることがわかった ここでクラスメソッドは、selfのようにメソッド自身ではなくclsクラスを第一引数に持っており、”そのクラス自身”または継承元のクラスを指している
変数app に代入されているcls.instance(**kwargs) におけるclsとは、 「Application」(自分自身)あるいは継承元の「SingletonConfigurable」クラスの両方を指すが、前者は .instance() というメソッドは持っていない
よって、SingletonConfigurable のインポート文 from traitlets.config.configurable import Configurable, SingletonConfigurable から、この部分 を調べれば良いことがわかる 結果、この部分 にめでたく instance(cls, *args, **kwargs)というクラスメソッドが見つかったので、次は今までの記述を遡りつつ解釈する作業を行うことになる 参考文献
iPython/ipykernel/ipykernel/kernelapp.py
iPython/ipython/IPython/core/shellapp.py
iPython/traitlets/traitlets/config/application.py
iPython/traitlets/traitlets/config/configurable.py
ここまで、わけわからん記述のソースコードにおける該当分はわかったけれど、じゃあどう動いているのかというとまだ全然理解できていない。読み込みをやっていく。