Python scriptの可搬性
ちょっとした作業のために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へのシンボリックリンク
ファイル置き場
https://github.com/nanigashi-uji/pytool_skeleton
https://gitlab.com/nanigashi_uji/pytool_skeleton
使い方
Pythonスクリプトを書き、lib/python/以下に置く。
% emacs lib/python/ex_greeting.py
bin以下に、Pythonスクリプト名から拡張子(.py)をとった名前、もしくは同名でbin/run_pyscriptへのシンボリックを作成する。
Pythonスクリプト作成
% (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
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スクリプトが正常に実行できるようになる。
モジュールをインストールした後の実行例
% ./bin/ex_greeting --date
./bin/ex_greeting --date
Hello, World! It is "Thu Jun 23 21:01:45 2021."
ImportError: No module named tzlocal
(おまけ) bin/mng_pymoduleの第一引数をdistcleanとすると、lib/python/site-packages/以下にあるものを全て消去する。
実行例
% ./bin/mng_pymodule distclean
% ls ./lib/python/site-packages/
%
自分で作成したスクリプトなど消してはいけないファイルをlib/python/site-packages/以下に置いてはいけない
↧