実行ファイルの置き換えに python を使用したときのメモです。Python 3 はプログラムコードを zip にアーカイブしたまま実行することができます。
●(1) Library として読み込む場合
テストコードを appmain.py, testmodule.py とします。
# appmain.py
import testmodule
def main():
testmodule.app()
if __name__ == '__main__':
main()
# testmodule.py
def app():
print( 'testmodule' )
そのまままとめて zip 圧縮可能で、Libpath に追加すると読み込むことができます。
### zip file を作る
import zipfile
src_list= [
'appmain.py',
'testmodule.py',
]
with zipfile.ZipFile( 'applib.zip', 'w' ) as fo:
for src in src_list:
fo.write( src )
### zip file 内の code を実行する
import sys
sys.path.append( 'applib.zip' )
import appmain
appmain.main()
embeddable 版の python3x.zip と同じように事前にコンパイルしておくこともできます。
●(2) zipapp (pyz) に変換してから実行する場合
zip ではなく zipapp (pyz) に変換すると script のように直接実行できるようになります。
予めサブフォルダ app を作って、その中に appmain.py, testmodule.py を入れておきます。下記のコードを実行すると pyz にまとめることができます。create_archive() 最初の引数 ‘app’ はフォルダ名です。
import zipapp
zipapp.create_archive( 'app', 'app.pyz', '/usr/bin/env python3', 'appmain:main' )
変換出力した app.pyz はただの zip ですが、エントリポイントとして __main__.py が挿入されています。この pyz ファイルはそのまま python で実行できます。
python app.pyz
また unix 系 os の場合は実行属性をつけることで直接実行することができます。
$ chmod 755 app.pyz
$ ./app.pyz
Windows の場合そのまま実行することはできませんが、pyz 専用の exe を作る方法が zipapp のマニュアルで紹介されています。
●(3) pyz の起動用に exe を作る
・zipapp — Manage executable Python zip archives
上記ページに載っている zastub.c をそのままコンパイルします。
// zastub.c
#define Py_LIMITED_API 1
#include "Python.h"
#define WIN32_LEAN_AND_MEAN
#include
#ifdef WINDOWS
int WINAPI wWinMain(
HINSTANCE hInstance, /* handle to current instance */
HINSTANCE hPrevInstance, /* handle to previous instance */
LPWSTR lpCmdLine, /* pointer to command line */
int nCmdShow /* show state of window */
)
#else
int wmain()
#endif
{
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
myargv[0] = __wargv[0];
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
return Py_Main(__argc+1, myargv);
}
コンパイル方法もマニュアル通りです。下記のコードを実行してコンパイルできます。
# compile.py
from distutils.ccompiler import new_compiler
import distutils.sysconfig
import sys
import os
from pathlib import Path
def compile(src):
src = Path(src)
cc = new_compiler()
exe = src.stem
cc.add_include_dir(distutils.sysconfig.get_python_inc())
cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
# First the CLI executable
objs = cc.compile([str(src)])
cc.link_executable(objs, exe)
# Now the GUI executable
#cc.define_macro('WINDOWS')
#objs = cc.compile([str(src)])
#cc.link_executable(objs, exe + 'w')
if __name__ == "__main__":
compile("zastub.c")
zastub.exe が作られるので、(2) で作った pyz とそのまま合成します。単純につなげるだけです。
copy /b zastub.exe + app.pyz app.exe
これで pyz を実行可能な app.exe ができました。そのまま実行できます。(python3.dll が必要)
app.exe
プログラムの内容によっては python の import でモジュールが見つからない場合があるかもしれません。その場合は予め検索パス設定しておいてください。環境変数 PYTHONPATH でも構いませんし、zastub.c の中で Py_SetPath() を使ってパスを与えることもできます。
zastub.c は python.exe 相当です。これが引数を何も加工していない状態になります。
int wmain()
{
return Py_Main( __argc, __wargv );
}
zastub.c では 1番目の引数に自分自身のパスを複製して挿入します。例えば引数が “arg1” なら「app.exe arg1」→ 「app.exe app.exe arg1」となります。自分自身である app.exe には app.pyz の内容がそのまま含まれているので、app.exe を pyz とみなしてそのまま実行できるわけです。
なお 1番目の引数を正しく渡すことができるなら、必ずしも exe に pyz を合成する必要がありません。例えば下のように直接 “app.pyz” を渡すことができます。
int wmain()
{
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
myargv[0] = __wargv[0];
myargv[1] = L"app.pyz";
return Py_Main(__argc+1, myargv);
}
ただし若干問題もあります。上のコードはカレントディレクトリでなければ app.pyz を読み込むことができません。例えば他のフォルダからフルパスで app.exe を実行しようとすると app.pyz が見つからずにエラーになります。もし app.pyz を実行ファイルと同じ場所に置くなら、__wargv[0] や GetModuleFileNameW() を利用して app.pyz のパスを求めることができます。
↓ __wargv[0] から app.pyz のパスを求める。
#define Py_LIMITED_API 1
#include
#define WIN32_LEAN_AND_MEAN
#include
#ifdef WINDOWS
int WINAPI wWinMain(
HINSTANCE hInstance, /* handle to current instance */
HINSTANCE hPrevInstance, /* handle to previous instance */
LPWSTR lpCmdLine, /* pointer to command line */
int nCmdShow /* show state of window */
)
#else
int wmain()
#endif
{
wchar_t *PYZ_NAME= L"app.pyz";
size_t NAME_LENGTH= wcslen( PYZ_NAME ) + 1;
size_t length= wcslen( __wargv[0] ) + NAME_LENGTH;
wchar_t *pyz_path= _alloca( (length + 2) * sizeof(wchar_t) );
wcscpy_s( pyz_path, length, __wargv[0] );
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
myargv[0] = __wargv[0];
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
wchar_t *eptr= pyz_path;
for( wchar_t *ptr= pyz_path ; *ptr ; ptr++ ){
if( *ptr == L'/' || *ptr == L'\\' ){
eptr= ptr + 1;
}
}
wcscpy_s( eptr, NAME_LENGTH, PYZ_NAME );
myargv[1]= pyz_path;
return Py_Main(__argc+1, myargv);
}
同じようにして exe の場所からライブラリの位置を特定し、Py_SetPath() することもできます。例えば embeddable 版を使うなら、Py_SetPath() に python3x.zip と dll/pyd フォルダの 2つを与えれば OK です。