すなぶろ

フリーランスのWebエンジニアがPythonを中心にプログラミング情報を紹介していきます。Rubyも書きますが、今はPython勉強中。一緒にPythonista目指しましょう!

IPythonにモジュールを自動リロードしてほしいとき

  1. IPython上でモジュールをimportする。
  2. コードスニペットを書いてテストしてみる。
  3. エディタでモジュールを書き直す。
  4. 反映させるにはIPythonごと再起動する必要がある。
  5. 「なんで?」ってなる。

では、自動化を始めましょう。



とってもキュートなIPython

IPython、便利ですよね。

  • コードの自動補完もあるし
  • 自作クラスでも推測してくれるし
  • シンタックスハイライトもあるし
  • インデントもよしなに管理してくれるし
  • Ctrl-↑とかで定義ごとさかのぼったりできるし
  • %saveで保存できるし

Wikipediaの記事によればクラスタ環境で並列コンピューティングも可能だとかで、ちょっと 'An enhanced Interactive Python' どころの騒ぎじゃない気もします。個人的には、貧弱なWindows環境でも拡張可能なシェルとして使えるのが嬉しいな、くらいです。

大抵のことを透過的にやってくれるあまり、「あっやばいEmacsのつもりでキー叩いちゃった」と思ったら「あれっ期待通りに動いてて良きかな?」みたいなことも結構あります。edsedなどストリームエディタのヘビーユーザーなら、そのままエディタとして使えるんじゃないでしょうか。素敵ですね。

しかしなぜでしょう。モジュールのリロードだけが(標準では)できません。いくらimportをしようが、一度読み込まれた定義が上書きされたり更新されることはないのです。モジュール間でimportが呼び出されまくってる場合に備えたメモライズ処理なのかもしれませんし、一種のPythonic Wayなのかもしれません。

[1]: import hack_challenge
[2]: hack_challenge.greet()
May the source be with you!

# -- greet関数を書き換える --

[3]: import hack_challenge
[4]: hack_challenge.greet()
May the source be with you!  # ←変わってない

[5]: quit

$ ipython
[1]: import hack_challenge
[2]: hack_challenge.greet()
Modules are red / DocStrings are blue / SyntaxSugar is sweet / And IPython here. # ←やっと反映される

複雑なモジュールを扱うならわかります。でも私は「ちょっとしたREPL」として使いたいので、せめて%reloadとかそういうコマンドがほしいのです。

ならば%autoreloadを使うが良い

かつて古いバージョンには%reloadというまんまなコマンドがあったらしいのですが、2008年当時の話なのでだいぶ昔です。しかしその代わり、今はautoreloadという拡張コマンドが用意されています。

[1]: %load_ext autoreload
[2]: %autoreload 2

こう宣言しておくとimportしたファイルが変更されるたびに自動的に読み込まれるので、 ログだけ見るととんでもない副作用が発生する関数のように見えます。 実際はただ合間にソースコードを変更しただけなんですけど。

f:id:sandmark:20171022012053j:plain
IPythonからの激励の言葉

%autoreload 22ってなんだよという方は公式ドキュメントをスポイルするか、Qiitaの記事(ipython を起動しながら自作モジュールを修正した場合)を参考にしてください。

%load_extも自動化したい

%load_ext autoreload, %autoreload 2という2行を入力することさえ手間だと感じる人、または構文やコマンドごと忘れた未来の自分のためにメモを残しておきます。

IPythonはシェルとして使用できると先ほど書きましたが、この機能を支えるのは『プロファイル』と呼ばれる設定ファイル群であり、細かく挙動を管理したり、複雑な設定を一発で切り替えることができます。bashで言えば.bash_profileのようなものですが、名前で管理したり、参照先のディレクトリを指定したりと、高い柔軟性を備えています。

私は今のところそこまで使い込む予定はないので、素直にデフォルトのプロファイルを作って設定ファイルを初期化します。

$ ipython profile create
$ ls ~/.ipython/profile_default/
db/ history.sqlite ipython_config.py log/ pid/ security/ startup/

これを行わないとipython_config.pyが生成されません。一から書いてもいいのですが、コマンドで生成される設定ファイルはコメントで雛形になっているのでおすすめです。

ipython_config.pyを開いて編集します。変更するのは32行目付近にあるc.InteractiveShellApp.exec_linesc.InteractiveShellApp.extensionsの2つです。

# lines of code to run at IPython startup.
c.InteractiveShellApp.exec_lines = [
    '%autoreload 2',
    'print("Notice: disabling autoreload in ipython_config.py will improve performance.")',
]

# A list of dotted module names of IPython extensions to load.
c.InteractiveShellApp.extensions = [
    'autoreload'
]

前者は起動時に実行するPythonコードのリスト、後者は自動的に読み込む拡張のリストです。

printは無くても構いませんが、どうも調べた結果autoreloadはかなりIPythonに負荷をかけるようなので、設定したことを忘れて「IPythonが重い!」などと言い出しかねない自分に向けてメッセージを残しておきます。

では、快適なPythonライフを。