PyInstallerで作成したexeが動かない人

2020-06-26

PyInstallerで作成したexeがちゃんと動かないという話です。

挙動としては、.exeの処理を進めても何も起こらないような感じです。

私と同じような状況で、PyInstallerで作成したexeが期待通りに動作しない、という人も結構いるのではないでしょうか。

.pyでは動作しているのに.exeにすると動作しないという。原因究明がわりと面倒な問題です。

Python

確認環境

  • Windows 10 Home(64bit)
  • Python 3.7(64bit)
  • PyInstaller 3.6でexe化

PyInstaller/noconsole/subprocessが原因

調べてみるとPyInstallerのnoconsoleオプションをつけると、プログラム内のsubprocessがうまく動作しないようです。

以下のブログ等を参考に修正してみたところ、動作させることができました。(日本語の情報は少ないですね。)

おそらく、上の英語のサイトを見て下の日本語のブログが書かれています。

それでも日本語で書かれているとありがたいです。

私にとっては「【悲報】PyInstallerさん、300MBのexeファイルを吐き出すようになる」が一番有用な情報だったのですが、PyInstaller関係のトラブル全般について書かれていることと、タイトルが私の欲しい情報とはまったく関係なかったのでなかなか見つけられませんでした。

かなり下の方の「subprocessモジュールを組み込んだアプリの --noconsole」の部分に書かれています。

他にもいろいろ読みましたが、解決に結びつけられるものはありませんでした。

解決方法としては

↓のソースをコピペ(「subprocess_args」の定義を追加)して

(↑コメントの日本語はほぼ機械翻訳)

subbrocessの実行時に

または

みたいな感じで呼び出すことでsubprocess問題が解決できました。

ちなみに修正前はsubprocess.check_output()ではなくsubprocess.run()を使用していました。

いつもの愚痴

要するに、PyInstallerがsubprocessに対応していないということかと思います。

この「noconsole」オプションが曲者なんでしょうね。32bitだと「Heur.AdvML.B」として誤検知されてしまいますし。

このコンソールを出す出さないとか設定させる+デフォルトが「出す」となっていることに猛烈な古さを感じてしまうのは私だけでしょうか。

昔のInstallShieldでコンソールを消すための設定を必死で探した記憶があります。

機能を実現するため以外の「おまじない」的なコードなんてできるだけ書きたくないんだけどな~

そして、PyInstallerさん。動かないものをexe化できちゃダメでしょう。exe化する段階でエラーにすべきです。複雑なプログラムだったら、原因究明に膨大な時間を費やすことになってしまいます。

背景 2020年09月追記

経緯を文字で伝えるのはなかなか難しいのですが…

元々、PyInstaller 3.6で普通にsubprocess_args()を定義せずにexe化したものでsubprocess.run("コマンド", shell=True)で動作させようとしたところ、動かなかったのでこの記事を書きました。(と思っています。)

そのときの内容は「subprocess_args()を定義して、subprocess.check_output("コマンド", **subprocess_args(False))で動きますよ」的なものでした。

その後、当方の開発環境をPyInstaller 4.0に更新。

そして昨日「subprocess.run("コマンド", **subprocess_args(True)で動きますよ」的なコメントをいただきました。

で、動作確認するにあたり、元々の原因であった(はずの)「subprocess.run("コマンド", shell=True)」で動作確認したところ、動いてしまいました。

ちょっと時間が空いてしまったので、記憶もなくなり、そのための勘違いもありそうなのですが、PyInstaller 3.6→4.0で修正されたということでしょうか。(万が一、時間があれば検証するかもしれません。)

ちなみに、PyInstaller 4.0の修正内容にそのような記載はありません。

どうなっているのでしょう。

なお当方の環境(PyInstaller 4.0)での、プログラムの動作は以下になっています。

処理
subprocess.run("コマンド")OK
subprocess.run("コマンド", shell=False)OK
subprocess.run("コマンド", **subprocess_args(True))OK
subprocess.run("コマンド", **subprocess_args(False))NG
subprocess.check_output("コマンド", **subprocess_args(True))NG
subprocess.check_output("コマンド", **subprocess_args(False))OK

大体上のような感じだと思います。

引数の詳細について詳しく知りたい方は自分で調べてみてください。

(「shell=False」とかはプログラムの内容次第かと思います。)

ちなみにsubprocess.check_output()は古い書き方のようです。

結論としては、

1.subprocess.run("コマンド")

これでダメなら

2.subprocess.run("コマンド", **subprocess_args(True))

ということになるかと思います。

ただし、ここを見ている方は基本的に動いていないはずですので、2になるのかと思います。

そもそも1で動くならsubprocess_args()の定義すら必要ないわけで、この記事も必要ないというか生まれてもいないはずなんですよね。やっぱりきっとPyInstaller 4.0で動作が変わったのでしょう。

基本的に現在は、

  • run()かPopen()を使うべき(call()、check_call()、check_output()は使う必要なし)
  • プロセスの終了を待ちたいならrun()、そうじゃないならPopen()

ということだと思います。

読者様からのコメント

当ページ一番下に読者様からのコメントがありますので、そちらも参考になるかもしれません。

参考サイト

個人的にはsubprocessについて「新しい内容が分かりやすくまとまった情報になっているサイト」があまりないと感じていましたが、以下のサイトを見つけました。

補足

PyInstaller関係について、以下にたくさん書きました。

何かの問題の解決のヒントになるかもしれません。是非読んでみてください。

以上。

後記

最後までお読みくださりありがとうございます。

こんなに更新したのに、現在はほぼ誰も見ていないゴミカスクズ無価値ブログです。

よければ(はてな)ブックマークや拡散をお願いいたします。

更新の原動力として励ましが欲しいのです…<(_ _)>

当ブログ内お薦め記事

記事はここまでです。