はじめに
7/1はこころの日❤️ nikkieです。
まだチュートリアルレベルですが、Pythonのパッケージに関してちょっとした魔法を会得しました。
目次
- はじめに
- 目次
- 見てください、この挙動! 同じ関数なのに、プラグインインストールの有無で動きが変わります!
- 小まとめ:Pythonパッケージでプラグイン
- setuptoolsのUser Guideより「Entry Points for Plugins」
- 関連情報
- 終わりに
- P.S. 「いつ使うのかわからないうちは使わなくてよい」
見てください、この挙動! 同じ関数なのに、プラグインインストールの有無で動きが変わります!
環境は Python 3.11.3 です。
venvを使った仮想環境で検証していきます。
まずtimmins
パッケージをインストールします。
% pip install 'timmins@git+https://github.com/ftnext/entry-points-suburi#subdirectory=docs-plugins-example/timmins' % pip list Package Version ---------- ------- pip 23.1.2 setuptools 68.0.0 timmins 0.1.0
timmins.hello_world
関数を実行します。
% python -c 'import timmins; timmins.hello_world()' Hello world
続いて、プラグインにあたるtimmins-plugin-fancy
もインストールします。
% pip install 'timmins-plugin-fancy@git+https://github.com/ftnext/entry-points-suburi#subdirectory=docs-plugins-example/timmins-plugin-fancy' % pip list Package Version -------------------- ------- pip 23.1.2 setuptools 68.0.0 timmins 0.1.0 timmins-plugin-fancy 0.1.0
再度timmins.hello_world
関数を実行します。
% python -c 'import timmins; timmins.hello_world()' !!! Hello world !!!
同じ関数を呼んでいるのに、出力される文字列が違いますよね?
プラグインの実装が使われているんです!
プラグインをアンインストールすると、出力は元に戻ります。
% pip uninstall -y timmins-plugin-fancy % python -c 'import timmins; timmins.hello_world()' Hello world
これ、すごくないですか!?
魔法っぽくないですか!?
ふふーん♪ 今日のnikkieはこの作り方が分かっちゃったんです。
小まとめ:Pythonパッケージでプラグイン
同じ関数timmins.hello_world
について
- プラグインがインストールされていない場合は「Hello world」と出力
- プラグインがインストールされている場合は、プラグインの実装を使って「!!! Hello world !!!」と出力
これをtimmins
は再インストールせずに実現できるんです(まさしくプラグイン!)。
Pythonパッケージのpluginの魔法、すげ〜〜〜🤩
— nikkie にっきー (@ftnext) 2023年7月1日
setuptoolsのドキュメントに沿って手を動かしましたhttps://t.co/LCPyQhGj1J
timminsパッケージだけをインストール
>>> timmins.hello_world()
Hello world
timmins-plugin-fancyも追加でインストール
>>> timmins.hello_world()
!!! Hello world !!!
では、どうやるかを見ていきましょう。
setuptoolsのUser Guideより「Entry Points for Plugins」
timmins
timminsの実装ですが、
- 与えられた文字列を出力する関数(
display
) - Hello worldを(displayを使って)出力する関数(
hello_world
)
に分かれます。
def display(text: str) -> None: print(text) def hello_world() -> None: display("Hello world")
このうちdisplay
をプラグインで上書きします。
これにはEntry Pointsを使います。
標準ライブラリのimportlib.metadata
インストールしたパッケージのメタデータを扱えるモジュールです。
ドキュメントの「概要」を見ると、pip install
したwheelパッケージのバージョンを取得する例があります。
ドキュメントの「エントリポイント」の例を今回使っています。
>>> from importlib.metadata import entry_points >>> display_eps = entry_points(group="timmins.display")
エントリポイントの集合のうち、groupが"timmins.display"
のエントリポイントを選択します1。
timminsはimportlib.metadataを使って、プラグインがあったらdisplayメソッドをロードする
groupが"timmins.display"
のエントリポイント(の1つ)をload
し2、それをdisplay
という名で指します。
つまり、プラグインがインストールされていたら、プラグインの実装を使ったdisplay
で動くように実装していたわけです。
プラグイン timmins-plugin-fancy
プラグイン側のポイントはプロジェクトの設定ファイルです。
今回はpyproject.toml
を使っています3。
timminsのプラグインは命名規則として、groupが"timmins.display"
のエントリポイントとする必要があります。
[project.entry-points."timmins.display"] excl = "timmins_plugin_fancy:excl_display"
excl_display
関数は「!」を加えてprint
する関数です。
def excl_display(text: str) -> None: print("!!!", text, "!!!")
timmins-plugin-fancyをインストールした状態でentry_points(group="timmins.display")
するとexcl_display
関数が見つかります。
なので、timmins
のdisplay
(という名前)はプラグインのexcl_display
関数を指した状態で動作し、「!」が加わったHello worldとなります。
関連情報
『Python実践入門』で知る
全然別目的で13章を読んでいたところ、「entry_pointsを利用したプラグイン機構」というコラムが!(13.5)
setuptoolsのドキュメントを検索し、「Entry Points for Plugins」を見つけました。
こんなPythonプロジェクトで使われている仕組みです!
有名なライブラリにはプラグインがありますよね。
プラグインってどう動いているのか深く追ったことはないのですが、仕組みは常々不思議に思っていました。
プラグインを実現するのに、エントリポイントが一役買っていました!
- pytest
pytest looks up the pytest11 entrypoint to discover its plugins,
- ref: https://docs.pytest.org/en/latest/how-to/writing_plugins.html#making-your-plugin-installable-by-others
- Sphinx
- Pygments
Applications can use entry points to load plugins; e.g. Pygments (a syntax highlighting tool) can use additional lexers and styles from separately installed packages.
- ref: https://packaging.python.org/en/latest/specifications/entry-points/
他にもまだまだあると思います🤗
終わりに
ライブラリの実装を変えていないのに、プラグインのインストールの有無で振る舞いが変わるのをどう実現しているか分かりました。
エントリポイントはconsole_scripts
の指定が多かったですが、プラグインにも一役買っていたのですね!
importlib.metadata
4を使って、プラグインの実装をロードできる!
これまでずっと少し不思議だったので、仕組みが分かって、しかも簡単な例で自分でも作れて、大変盛り上がっています🙌
今回のリポジトリです:
P.S. 「いつ使うのかわからないうちは使わなくてよい」
「いつ使うの?」と思った方、いまのあなたは使う必要がないということです!
2021年11月の #stapy より。#expertpython #エキPy の訳者の1人、森本さんに話していただきました
— nikkie にっきー (@ftnext) 2023年6月24日
メタプログラミングを例に
「いつ使うのかわからないうちは使わなくてよい」
これは激しく同感です。
必要としている人は理由(使い所)がわかっている
資料やアーカイブ:https://t.co/hmbbJ4hCN5 pic.twitter.com/JEHyJwkS8y
- ドキュメントより「Equivalently, since entry_points passes keyword arguments through to select」↩
- ドキュメントより「値を解決する .load() メソッド」↩
- setup.pyはどうしても必要ではないなら避けよう(setup.cfgかpyproject.tomlを使おう)。ref:https://setuptools.pypa.io/en/latest/userguide/quickstart.html#setup-py↩
- Python 3.8で追加され、3.10で暫定的なものではなくなっています。暫定版だと使いづらい向きにはサードパーティのimportlib-metadataがあります↩