概要
Python 名前空間パッケージによるライブラリ分割フレームワーク Microlib のアイデアにより、大きなライブラリを機能別にインストールでき、かつ単一リポジトリで開発するための手法を紹介します。
問題点として
ライブラリの開発規模が大きくなるに連れて、ある機能が必要とする外部ライブラリは別の機能では不要になる、という状況が起こります。
例えば画像処理やネットワーク処理および機械学習を行うライブラリを開発する場合、 OpenCV や Tensorflow などの外部ライブラリを担当外の機能にインストールするには規模が大きく効率が悪くなります。
実現するための手段として
こちらのブログ記事に書かれている、名前空間パッケージを使用したライブラリ分割のアイデア、 Python microlib により、各機能ごとの独立性を高めます。
ブログ記事中には5つのゴールが設定されていますが、この Qiita 記事では特につぎの点を取り上げます。
- 各 microlib はそれぞれが "pip install" 可能で、インストール時にはその microlib が必要な外部ライブラリをインストールする。
- microlib が他の microlib に依存する場合は、インストール時に他の microlib も自動的にインストールされる。
- 全ての microlib を含むライブラリ全体を1つのパッケージとしてインストールすることもできる。
Python の名前空間パッケージについて
概要
- 名前空間パッケージは通常のパッケージで必要なマーカーファイル
__init__.py
を置く必要はありません。 - 同名の名前空間パッケージは別の場所に保存されていても自動的にまとめられます。
例
次のような構成となっている Python ライブラリがあるとします。
2箇所にある mylib
には __init__.py
はありません。 この mylib
は名前空間パッケージになります。
.
|____group1
| |______init__.py
| |____mylib ← 名前空間パッケージ
| |____func_a.py
|____group2
|______init__.py
|____mylib ← 名前空間パッケージ
|____func_b.py
def create_message1() -> None:
return "Message 1"
def create_message2() -> None:
return "Message 2"
このサンプルを実行する場合
>>> from group1.mylib.func_a import create_message1
>>> from group2.mylib.func_b import create_message2
>>> print(create_message1())
Message 1
>>> print(create_message2())
Message 2
mylib は通常のパッケージではありませんが、名前空間パッケージとして読み込まれます。
これにより中の関数を実行することができました。
microlib フレームワーク
概要
ブログ記事中で紹介されている構成は通常のパッケージと名前空間パッケージの組み合わせです。
.
|____microlibs
|____bar ← pip install 可能なトップレベルパッケージ
| |____requirements.txt
| |____setup.py
| |____macrolib ← 名前空間パッケージ
| |____bar ← 通常のパッケージ
| |______init__.py
| |____module1.py
| |____moduleN.py
|____foo ← pip install 可能なトップレベルパッケージ
|____requirements.txt
|____setup.py
|____macrolib ← 名前空間パッケージ
|____foo ← 通常のパッケージ
|______init__.py
|____module1.py
|____moduleN.py
- microlibs/bar と microlibs/foo はそれぞれが独立して pip install 可能な通常の Python パッケージです。
- 紛らわしいのですが
macrolib
が名前空間パッケージになります。 - これにより個別にインストールされても macrolib は共通の名前空間としてまとめられます。
- 使用する側は
from macrolib.bar.module1.py import func_a
やfrom macrolib.foo.module2.py import func_b
のように呼び出すことができます。
例
サンプルプログラム
Microlib フレームワークを実現する最小限のサンプルを作成しました。
サンプルプログラム: mtools
.
|____setup.py ← 全体を一括して pip install する場合のための setup スクリプト。
|____README.md
|____microlibs
|____message_util_module ← pip install 可能なトップレベルパッケージ
| |____mtools ← 名前空間パッケージ
| | |____message_util ← 通常のパッケージ
| | |______init__.py ← 通常のパッケージに必要なマーカーファイル
| | |____greeting.py ← 挨拶文を返す関数 `get_greeting_message` が入っています。
| |____setup.py ← mtools.message_util を pip install するための setup スクリプト。
| |____requirements.txt ← PyPi 以外の外部ライブラリがある場合はこちらに記述します。
|____clock_util_module ← pip install 可能なトップレベルパッケージ
|____mtools ← 名前空間パッケージ
| |____clock_util ← 通常のパッケージ
| |______init__.py ← 通常のパッケージに必要なマーカーファイル
| |____local_time.py ← 各地域の現地時刻を返す `get_local_time` が入っています。
|____setup.py ← mtools.clock_util を pip install するための setup スクリプト。
|____requirements.txt ← PyPi 以外の外部ライブラリがある場合はこちらに記述します。
- このサンプルは1つのリポジトリとして管理されています。
- 共通の名前空間
mtools
が使用可能になります。 - 2つの microlib、
message_utils_module
とclock_util_module
を内包しています。 - この2つの microlib は個別に pip install することが可能です。
- また一括して pip install することも可能です。
- message_util_module をインストールすると自動的に clock_util_module がインストールされます。
以下、サンプルを基に解説します。
インストール
まず、全ての microlib を一括してインストールする場合の例です。
このサンプルのルートで実行した場合です。
$ pip install .
(略)
Installing collected packages: mtools
Running setup.py install for mtools ... done
Successfully installed mtools-0.1.0
$ pip freeze | grep mtools
mtools==0.1.0
mtools-clock-util==0.1.0
mtools-message-util==0.1.0
1回の pip install で全ての microlib がインストールされました。
トップディレクトリの setup.py の中で、各 microlib を個別に pip install するコードが実行されたためです。
次に各 microlib を個別にインストールする場合の例です。
$ cd microlibs/clock_util_module/
$ pip install -r requirements.txt
(略)
Installing collected packages: mtools-clock-util
Running setup.py install for mtools-clock-util ... done
Successfully installed mtools-clock-util-0.1.0
$ pip freeze | grep mtools
mtools-clock-util==0.1.0
$ cd ../message_util_module/
$ pip install -r requirements.txt
(略)
Installing collected packages: mtools-message-util
Running setup.py install for mtools-message-util ... done
Successfully installed mtools-message-util-0.1.0
$ pip freeze | grep mtools
mtools-clock-util==0.1.0
(略)#egg=mtools_message_util&subdirectory=microlib_tools/microlibs/message_util_module
各々のディレクトリで pip install -r requirements.txt
を行い, それぞれの microlib をインストールすることができました。
requirements.txt の最後の行に -e .
が入っているのが肝心な点になります。
これにより自身の microlib と依存 microlib がインストールされる仕組みになっています。
依存する外部ライブラリや 他の microlib モジュールについて
ブログ記事中でも setup tools と requirements.txt の区別が難しい点が書かれています。
- setup.py
- PyPiからインストールできるパッケージはこちらに記載しインストールします。
- requirements.txt
- PyPi以外のパッケージをここに記述します。
- 依存する microlib もここに記述します。
ブログ記事中では次のような工夫がされています。
-
トップディレクトリから一括してインストールする場合
-
pip install .
もしくはpip install -e .
によりトップディレクトリの setup.py を実行して、コードにより各 microlib をインストールします。
-
-
microlib を個別にインストールする場合
-
pip install -r requirements.txt
により、依存する他の microlib と共にインストールします。
-
実行
結果的にこのサンプルは次のような動作をします。
>>> from mtools.clock_util.local_time import get_local_time
>>> dt = get_local_time("Asia/Tokyo")
>>> dt.strftime('%Y/%m/%d %H:%M:%S')
'2018/07/09 17:09:51'
>>> from mtools.message_util.greeting import get_greeting_message
>>> get_greeting_message("Asia/Tokyo")
'こんにちは。'
複数のディレクトリに存在する mtools
の各機能はまとめられ、あたかも1つのパッケージであるかのように実行することができました。
今後の課題と感じられた点
- 一括インストールした場合、各 microlib をまとめるだけで機能を持たないトップレベルのパッケージもインストールされる。
- 個別に microlib をインストールする場合は
pip install -r requirements
になるため紛らわしい。 - アンインストールする際は各 microlib を1つづつ
pip uninstall
する必要がある。
追記
(2018/07/27)
pip 10系を使用する場合はサンプルで使用されている pip.main()
が使用できなくなっています。
その場合は次のコードで同じように動作します。
import subprocess
subprocess.run(["pip", "install", x], stdout=subprocess.PIPE)
# pip install -e . の場合は
subprocess.run(["pip", "install", "-e", "."], stdout=subprocess.PIPE)