Python scriptの可搬性
2021/8/31 Python2とPython3のスクリプトが混在していても使えるようにバージョンアップした
ちょっとした作業のためにPythonでひとまとりのスクリプトを書くことが多々ある。Pythonを利用する利点は、既存のライブラリ/モジュールが豊富で、OSのパッケージシステム(rpm,yum,apt,..)やそれに準じたもの(Macports, Homebrew)で配布されていたり、PyPI(pipコマンド一つ)によって容易に導入して利用できる点にある。
ただし、いろいろな計算機で可搬的に利用しようとすると、環境依存を気にしなくてはいけなくなる。モジュールがOSの配布物より導入済みの場合とそうでない場合、システム管理者としてpipを使える環境とそうでない場合、一般ユーザーとしてPyPIの設定をカスタマイズしている場合とそうでない場合、virtualenvを使っている場合とそうでない場合、などなど。ちょっとしたツールをパッと使いたいのに、各環境でPythonのセットアップがどうなっていたかを気にしないといけないのは非常に煩わしい。
それを回避するための処方箋をテンプレート化した。
方針
-
利用する環境ですでに導入済みかどうかは気にせず、とにかくローカルにPyPIを利用してインストールして利用する。そのためのシェルスプリプトを用意する。(内部的では
pip install --target ./lib/python/site-packages some-moduleを実行する) -
利用するPythonを実行する前に、PyPIでインストールしたローカルのディレクトリを環境変数
PYTHONPATHを加えて実行する必要があるので、そのためのラッパーシェルスクリプトを用意する。 -
ただし、Pythonスクリプト毎にラッパーシェルスクリプトを書くのは不毛な作業なので、一つのシェルスクリプトに対してシンボリックリンクを張り、シンボリック名に応じて実行するPythonスクリプトを切り替える。
実装 (含むサンプルpythonスクリプト)
ファイルの一覧としては、以下。 PyPIモジュールをインストールするbashスクリプトと、実際にPythonスクリプト実行するラッパースクリプトでは、ディレクトリ名などで整合性をとる必要がある。そのため実態としては1つのファイルとして、シンボリックリンクを使って実行時のコマンド名で動作を切り替える。それにより、内部のシェル変数の定義の記述は一箇所にまとめている。
結果的に、Pythonスクリプト以外の実体としては1つのシェルスクリプトに収まっている。
bin/run_pyscript
bin/mng_pymodule -> run_pyscript
bin/ex_greeting -> run_pyscript
lib/python/ex_greeting.py
lib/python/site-packages/
-
bin/mng_pymodule: 指定したPyPIモジュールのインストール/一括アンインストールをする。 -
bin/run_pyscript: シェルスクリプトの実体。この名前で実行したときは、第一引数に指定した名前でlib/python以下に置かれたpython scriptを、環境変数PYTHONPATHにlib/pythonとlib/python/site-packagesを加えたうえで実行する。 -
lib/python: Pythonスクリプトを置くディレクトリ -
lib/python/site-packages/: PyPIでPythonモジュールがローカルにインストールされるディレクトリ
以下は、サンプル。
-
lib/python/ex_greeting.py: サンプルスクリプト("Hello, world!"+時刻表示)。標準モジュール以外に、pytz, tzlocalを使う。 -
bin/ex_greeting: 上記サンプルを呼び出すためのコマンドとして使うためのbin/run_pyscriptへのシンボリックリンク
2021/8/31追記
Python2とPython3の混在に対応するため、下記のシンボリックリンクとサンプル例を追加した
bin/mng_pymodule2 -> run_pyscript
bin/mng_pymodule3 -> run_pyscript
bin/ex_greeting2 -> run_pyscript
bin/ex_greeting3 -> run_pyscript
lib/python/ex_greeting2.py
lib/python/ex_greeting3.py
ファイル置き場
使い方
- Pythonスクリプトを書き、
lib/python/以下に置く。
% emacs lib/python/ex_greeting.py
-
bin以下に、Pythonスクリプト名から拡張子(.py)をとった名前、もしくは同名でbin/run_pyscriptへのシンボリックを作成する。
% (cd bin ; ln -s run_pyscript ex_greeting)
この時点でも、必要なモジュールがpython実行環境に揃っていれば問題なく実行できるはずだが、揃ってない場合には、モジュールがないというエラーがでる。
% ./bin/ex_greeting --date
Traceback (most recent call last):
File "......../lib/python/ex_greeting.py", line 8, in <module>
import tzlocal
ImportError: No module named tzlocal
- Pythonスクリプトで使用しているPyPIのモジュール(以下の例では
tzlocal)をインストールする。
% ./bin/mng_pymodule install tzlocal pytz
Collecting tzlocal
Using cached tzlocal-2.1-py2.py3-none-any.whl (16 kB)
Collecting pytz
Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
Installing collected packages: pytz, tzlocal
Successfully installed pytz-2021.1 tzlocal-2.1
% ls
./lib/python/site-packages/pytz pytz-2021.1.dist-info tzlocal tzlocal-2.1.dist-info
%
bin/mng_pymoduleの第一引数のinstallは省略可能である。実行後はPythonスクリプトが正常に実行できるようになる。
2021/8/31追記
python2とpython3のファイル/モジュールを混在して使用するために、mng_pymodule2,mng_pymodule3というシンボリックリンクを用意した。mng_pymodule2はpip2があればそれを使い、なければpipを実行する。mng_pymodule3,mng_pymoduleは、pip3があればpip3を使い、なければpipを使う。ただし、どちらも環境変数PIPで使いたいpipが指定されている場合には、そちらが優先して使われる。 モジュールがインストールされる場所は、
./lib/python/site-packages/X.Y/ (X,Yはそれぞれpythonのメジャーバージョン,マイナーバージョン)となる。
% ./bin/ex_greeting --date
./bin/ex_greeting --date
Hello, World! It is "Thu Jun 23 21:01:45 2021."
[2021/8/31追記] Python2とPython3の使い分け
スクリプトを実行する際に、どのバージョンのpythonを用いるかは、スクリプトのShebang(シェバン)に従う動作とした。サンプルファイルでは
% diff3 lib/python/ex_greeting.py lib/python/ex_greeting2.py lib/python/ex_greeting3.py
====
1:1c
#!/usr/bin/env python
2:1c
#!/usr/bin/env python2
3:1c
#!/usr/bin/env python3
違いはShebangだけである。これらを(呼ぶシンボリックリンクを)実行すると、
% env ./bin/ex_greeting -d | head -2
Hello, World! It is "Tue Aug 31 20:49:16 2021."
Python : 2.7.18 (....)
% env ./bin/ex_greeting2 -d | head -2
Hello, World! It is "Tue Aug 31 20:49:19 2021."
Python : 2.7.18 (....)
% env ./bin/ex_greeting3 -d | head -2
Hello, World! It is "Tue Aug 31 20:49:21 2021."
Python : 3.8.2 (....)
といったようにPythonが使い分けられる。ただし環境変数PYTHONが指定された場合には、そちらを優先的に使う。下の2番目の実行例のように、環境変数PYTHONとスクリプトファイルのShebangのpythonのメジャーバージョンが違っている場合には警告を出すようにした。
% env PYTHON=python3 ./bin/ex_greeting -d | head -2
Hello, World! It is "Tue Aug 31 20:49:37 2021."
Python : 3.8.2 (....)
% env PYTHON=python3 ./bin/ex_greeting2 -d | head -3
Warning: Python version (3.8) mismatch? (Version 2) is expexted by /..../lib/python/ex_greeting2.py)
Hello, World! It is "Tue Aug 31 20:50:07 2021."
Python : 3.8.2 (....)
おまけ
bin/mng_pymoduleの第一引数をdistcleanとすると、lib/python/site-packages/以下にあるものを全て消去する。
% ./bin/mng_pymodule distclean
% ls ./lib/python/site-packages/
%
自分で作成したスクリプトなど消してはいけないファイルをlib/python/site-packages/以下に置いてはいけない
[2021/8/31追記]
さらに、
% ./bin/mng_pymodule2 clean
% ./bin/mng_pymodule3 clean
と実行した場合には、対応するpythonバージョンのモジュールのみ(対応する./lib/python/site-packages/X.Y/以下のみ)をクリーンナップする動作とした。