2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonライブラリを小分けにInstallできるレポジトリ設計

Last updated at Posted at 2018-07-24

概要

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

group1/mylib/func_a.py
def create_message1() -> None:
    return "Message 1"
group2/mylib/func_b.py
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_afrom 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_moduleclock_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)

参考

Microlib
PEP-420
Python 3.3b1 の名前空間パッケージを試してみた

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?