2022/08/10(水) [n年前の日記]
#1 [python][windows] Pythonスクリプトをexe化したい
Windows10 x64 21H2 + Python 3.8.10 x64 上で、Pythonスクリプトをexeファイルにしたい。pygame を使ってWindows用のスクリーンセーバを作ってみたいのだけど、そのためには Pythonスクリプトファイルをexe化しないといけないので…。
今回は virtualenv を使って、Python 3.8 が動作する環境を作った。と言うのも、各exe化ツールは動作に不要なファイルまで含めてexe化してしまう時があるそうで。故に、virtualenv や venv 等を使って、動作に必要な最低限のモジュールだけがインストールされている環境を用意して、ソレをexe化するのが望ましいのだとか。
Pythonスクリプトをexe化するツールは色々あるけれど、今回は PyInstaller と py2exe を試用してみた。
virtualenv で環境を作った場合、PyInstaller、py2exe も、その環境にインストールして、その環境内で実行しないといけない。そうしないと、システム側にインストールした PyInstaller や py2exe を使ってしまって、動作に不要なモジュールまで入ってしまう。
ちなみに、昔の py2exe は Python 3.4 までしか対応してなかったらしいけど、今現在の py2exe 0.11.1.1 は Python 3.7 - 3.10 まで対応している。
_py2exe - PyPI
今回は virtualenv を使って、Python 3.8 が動作する環境を作った。と言うのも、各exe化ツールは動作に不要なファイルまで含めてexe化してしまう時があるそうで。故に、virtualenv や venv 等を使って、動作に必要な最低限のモジュールだけがインストールされている環境を用意して、ソレをexe化するのが望ましいのだとか。
Pythonスクリプトをexe化するツールは色々あるけれど、今回は PyInstaller と py2exe を試用してみた。
virtualenv で環境を作った場合、PyInstaller、py2exe も、その環境にインストールして、その環境内で実行しないといけない。そうしないと、システム側にインストールした PyInstaller や py2exe を使ってしまって、動作に不要なモジュールまで入ってしまう。
> env38\Scripts\activate (env38) ... > python -V Python 3.8.10 (env38) ... > python -m pip install pyinstaller -U (env38) ... > python -m pip install py2exe -U (env38) ... > python -m pip list | grep -e pyinstaller -e py2exe py2exe 0.11.1.1 pyinstaller 5.3 pyinstaller-hooks-contrib 2022.8 (env38) ... > deactivate
ちなみに、昔の py2exe は Python 3.4 までしか対応してなかったらしいけど、今現在の py2exe 0.11.1.1 は Python 3.7 - 3.10 まで対応している。
_py2exe - PyPI
◎ PyInstallerでexe化。 :
Python 3.8.10 + PyInstaller 5.3 を使って、先日作成した、tkinterのウインドウ内にpygameのウインドウを埋め込むスクリプト、01_embed_pygamewindow.py をexe化してみた。
_01_embed_pygamewindow.py
以下を打てば、exe化される。
distディレクトリ以下に、01_embed_pygamewindow.exe が作成された。実行したらちゃんと動作した。
ただ、ウインドウが開くまでちょっと時間がかかる。
PyInstaller で作成したexeは、実行すると C:\Users\USERNAME\AppData\Local\Temp\ 以下に仮ディレクトリを作成して、その中に実行に必要なファイルを全て解凍して、それらのファイルを実行しているらしい。また、アプリを終了すると、その仮ディレクトリも削除される。毎回実行するたびに、圧縮ファイルを解凍する処理が行われるので、起動が遅いのも当然だろうか。
ちなみに、一度 exeファイルに変換すると、.specファイルが作られる。その .specファイルの内容を修正して、pyinstaller hoge.spec とすれば、その .specファイルの内容に従って exeファイルが作成される。ただし、.spec を使わないで exe を作成すると、既に存在していた .spec は上書きされてしまうので注意。
余談。今回、実行ファイルを圧縮してファイルサイズを小さくしてくれる、UPX というツールを使わないように、--noupx を指定してみたのだけど。生成された exeファイルは、--noupx を指定しない場合と変わらないファイルサイズだった…。このオプション、実は機能していないのでは…?
_01_embed_pygamewindow.py
以下を打てば、exe化される。
pyinstaller --onefile --noconsole --noupx 01_embed_pygamewindow.py
- --onefile : 1つのexeファイルにする。
- --noconsole : exe実行時にコンソール(DOS窓)を開かない。
- --noupx : 実行ファイル圧縮ツール UPX を使わない。
distディレクトリ以下に、01_embed_pygamewindow.exe が作成された。実行したらちゃんと動作した。
ただ、ウインドウが開くまでちょっと時間がかかる。
PyInstaller で作成したexeは、実行すると C:\Users\USERNAME\AppData\Local\Temp\ 以下に仮ディレクトリを作成して、その中に実行に必要なファイルを全て解凍して、それらのファイルを実行しているらしい。また、アプリを終了すると、その仮ディレクトリも削除される。毎回実行するたびに、圧縮ファイルを解凍する処理が行われるので、起動が遅いのも当然だろうか。
ちなみに、一度 exeファイルに変換すると、.specファイルが作られる。その .specファイルの内容を修正して、pyinstaller hoge.spec とすれば、その .specファイルの内容に従って exeファイルが作成される。ただし、.spec を使わないで exe を作成すると、既に存在していた .spec は上書きされてしまうので注意。
余談。今回、実行ファイルを圧縮してファイルサイズを小さくしてくれる、UPX というツールを使わないように、--noupx を指定してみたのだけど。生成された exeファイルは、--noupx を指定しない場合と変わらないファイルサイズだった…。このオプション、実は機能していないのでは…?
◎ py2exeでexe化。 :
Python 3.8.10 + py2exe 0.11.1.1 で exe化してみる。
変換したい .py と同じ階層に setup.py を作成。この setup.py の記述で、以下のような指定ができるらしい。
_setup.py
python setup.py py2exe と打てば、distディレクトリ以下にexeファイルが生成される。実行したところ、ちゃんと動作してくれた。なんとなくだけど、PyInstaller と比べると起動は若干速いような気がした。
ただ、今回、"bundle_files": 1, を指定して、1つのexeファイルにするように指定したはずが、そうはなってくれなかった。
どうやら、py2exe を使って1つのexeファイルにするのは難しい模様…。となると、pygame でスクリーンセーバを作るとしたら、PyInstaller 一択のようだなと。
変換したい .py と同じ階層に setup.py を作成。この setup.py の記述で、以下のような指定ができるらしい。
- どのスクリプトを exe化するか。
- 1つのファイルにするか、複数のファイルにするか。
- 動作にはどんなモジュールが必要か。
_setup.py
from distutils.core import setup import py2exe option = { "packages": ["pygame", "tkinter", "win32gui"], "compressed": 1, "optimize": 2, "bundle_files": 1, } setup( options = { 'py2exe': option, }, console = [ {'script': "01_embed_pygamewindow.py"} ], zipfile = None, )
python setup.py py2exe と打てば、distディレクトリ以下にexeファイルが生成される。実行したところ、ちゃんと動作してくれた。なんとなくだけど、PyInstaller と比べると起動は若干速いような気がした。
ただ、今回、"bundle_files": 1, を指定して、1つのexeファイルにするように指定したはずが、そうはなってくれなかった。
- distディレクトリ内には、pygame の動作に必要になるSDL関連dllファイルが9ファイルほどコピーされた。
- dist/libディレクトリも作成されて、その中には tkinter の動作に必要なのであろう tcl と tk というフォルダがあり、中には1000ファイルほど入っていた。
> dir ... 2022/08/08 05:16 <DIR> . 2022/08/08 05:16 <DIR> .. 2022/08/08 05:16 9,367,824 01_embed_pygamewindow.exe 2022/08/08 05:16 <DIR> lib 2021/05/03 11:54 3,406,016 libcrypto-1_1.dll 2021/05/03 11:54 32,792 libffi-7.dll 2021/05/03 11:54 690,368 libssl-1_1.dll 2022/08/08 01:48 300,544 SDL.dll 2022/08/08 01:48 2,326,016 SDL_image.dll 2022/08/08 01:48 165,888 SDL_mixer.dll 2022/08/08 01:48 623,616 SDL_ttf.dll 2021/05/03 11:54 1,705,120 tcl86t.dll 2021/05/03 11:54 1,468,064 tk86t.dll 10 個のファイル 20,086,248 バイト 3 個のディレクトリ 88,550,535,168 バイトの空き領域 > dir lib ... 2022/08/08 05:16 <DIR> . 2022/08/08 05:16 <DIR> .. 2021/05/08 06:29 <DIR> tcl 2021/05/08 06:29 <DIR> tk 0 個のファイル 0 バイト 4 個のディレクトリ 88,550,535,168 バイトの空き領域
どうやら、py2exe を使って1つのexeファイルにするのは難しい模様…。となると、pygame でスクリーンセーバを作るとしたら、PyInstaller 一択のようだなと。
[ ツッコむ ]
#2 [python][windows] Windows用スクリーンセーバの呼び出し動作をPythonで確認
Windows用スクリーンセーバを、Python を使って作ってみたい。
環境は、Windows10 x64 21H2 + Python 3.8.10 x64。
まずは、Windows用スクリーンセーバが呼び出される時、どんなオプションが渡されているのかを念のために調べておきたい。そこで、渡されたオプションを、デスクトップ上のテキストファイルに追記していくだけの Pythonスクリプトを書いてみた。
_01_scrsavchk.py
このスクリプトファイルを、PyInstaller を使ってexe化する。
01_scrsavchk.exe を、01_scrsavchk.scr にリネーム。Windows用スクリーンセーバの実行ファイル、.scr の実体は、拡張子を .scr にリネームした .exeファイルなので…。
今回、Python 3.8.10 x64 (64bit版) でexe化したので…。
ということで、01_scrsavchk.scr を、C:\Windows\System32\ 以下にコピーした。
スクリーンセーバとして選んでみる。
スクリーンセーバ一覧の中に、01_scrsavchk という種類が増えている。これを選ぶと、DOS窓が開いて、2秒ほど経ったら閉じる。「設定」「プレビュー」ボタン等も押してみて様子を伺う。
デスクトップに、temp_log.txt というテキストファイルができているはず。中身を確認。
そんなわけで、巷の解説通り、Windows用スクリーンセーバは、以下の3種類のオプションを渡した状態で実行されることが分かった。
また、「設定」「プレビュー」ボタンを押して実行・終了した直後、すぐさま /p xxxxx を渡して実行されていることも分かった。
Windows用のスクリーンセーバは多重起動を禁止するように作るべしと言われているけど、このあたりの呼び出しタイミングがシビア、ということだろうか…。それとも、既にフルスクリーンで起動してるのに、再度実行されてしまう場面もあり得るのだろうか…。
環境は、Windows10 x64 21H2 + Python 3.8.10 x64。
まずは、Windows用スクリーンセーバが呼び出される時、どんなオプションが渡されているのかを念のために調べておきたい。そこで、渡されたオプションを、デスクトップ上のテキストファイルに追記していくだけの Pythonスクリプトを書いてみた。
_01_scrsavchk.py
import os import sys import datetime import time def main(): desktop_dir = os.path.expanduser('~/Desktop') log_file = os.path.join(desktop_dir, "temp.log.txt") dt_now = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") print("Screensaver option check") print("%s" % dt_now) print("look %s" % log_file) kind = "none" hwnd = 0 if len(sys.argv) >= 2: s = sys.argv[1][0:2].lower() if s == "/s": # full screen kind = "fullscreen" elif s == "/c": # config / settings kind = "config" elif s == "/p": # preview kind = "preview" hwnd = int(sys.argv[2]) with open(log_file, 'a') as f: f.write("[%s] %s , [%s]\n" % (dt_now, kind, " ".join(sys.argv))) time.sleep(2) if __name__ == '__main__': main()
このスクリプトファイルを、PyInstaller を使ってexe化する。
pyinstaller --onefile --noupx 01_scrsavchk.pydistディレクトリに、01_scrsavchk.exe というexeファイルが作られた。
01_scrsavchk.exe を、01_scrsavchk.scr にリネーム。Windows用スクリーンセーバの実行ファイル、.scr の実体は、拡張子を .scr にリネームした .exeファイルなので…。
今回、Python 3.8.10 x64 (64bit版) でexe化したので…。
- 64bit版のプログラム → C:\Windows\System32\ 以下に入れる。
- 32bit版のプログラム → C:\Windows\SysWOW64\ 以下に入れる。
ということで、01_scrsavchk.scr を、C:\Windows\System32\ 以下にコピーした。
スクリーンセーバとして選んでみる。
- Windows10の左下の検索欄に「スクリーンセーバー」と打ち込んで、リストアップされた「スクリーンセーバーの変更」を起動。
- もしくは、デスクトップを右クリック → 個人用設定 → ロック画面 → スクリーンセーバー設定、でもいい。
スクリーンセーバ一覧の中に、01_scrsavchk という種類が増えている。これを選ぶと、DOS窓が開いて、2秒ほど経ったら閉じる。「設定」「プレビュー」ボタン等も押してみて様子を伺う。
デスクトップに、temp_log.txt というテキストファイルができているはず。中身を確認。
[2022/08/10 19:51:13] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114] [2022/08/10 19:51:21] config , [C:\WINDOWS\system32\01_scrsavchk.scr /c:330168] [2022/08/10 19:51:24] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114] [2022/08/10 19:51:29] fullscreen , [C:\WINDOWS\system32\01_scrsavchk.scr /s] [2022/08/10 19:51:32] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114] [2022/08/10 19:51:41] preview , [C:\WINDOWS\system32\01_scrsavchk.scr /p 68114]これで、Windows10 がどんなオプションをつけてスクリーンセーバを呼び出しているのかが分かる。
そんなわけで、巷の解説通り、Windows用スクリーンセーバは、以下の3種類のオプションを渡した状態で実行されることが分かった。
- /s : フルスクリーン表示。
- /c:xxxxx : 設定画面表示。
- /p xxxxx : スクリーンセーバー設定画面内のプレビュー表示。xxxxx はウインドウハンドル。
また、「設定」「プレビュー」ボタンを押して実行・終了した直後、すぐさま /p xxxxx を渡して実行されていることも分かった。
Windows用のスクリーンセーバは多重起動を禁止するように作るべしと言われているけど、このあたりの呼び出しタイミングがシビア、ということだろうか…。それとも、既にフルスクリーンで起動してるのに、再度実行されてしまう場面もあり得るのだろうか…。
[ ツッコむ ]
#3 [nitijyou] 日記ページをアップロード
2022/07/13を最後に日記をアップロードしてなかったのでアップロード。
[ ツッコむ ]
以上、1 日分です。