PyInstallerで作成したexeが動かない人
PyInstallerで作成したexeがちゃんと動かないという話です。
挙動としては、.exeの処理を進めても何も起こらないような感じです。
私と同じような状況で、PyInstallerで作成したexeが期待通りに動作しない、という人も結構いるのではないでしょうか。
.pyでは動作しているのに.exeにすると動作しないという。原因究明がわりと面倒な問題です。
確認環境
- Windows 10 Home(64bit)
- Python 3.7(64bit)
- PyInstaller 3.6でexe化
PyInstaller/noconsole/subprocessが原因
調べてみるとPyInstallerのnoconsoleオプションをつけると、プログラム内のsubprocessがうまく動作しないようです。
以下のブログ等を参考に修正してみたところ、動作させることができました。(日本語の情報は少ないですね。)
- Recipe subprocess · pyinstaller/pyinstaller Wiki - GitHub(英語)
- 【悲報】PyInstallerさん、300MBのexeファイルを吐き出すようになる(日本語)
おそらく、上の英語のサイトを見て下の日本語のブログが書かれています。
それでも日本語で書かれているとありがたいです。
私にとっては「【悲報】PyInstallerさん、300MBのexeファイルを吐き出すようになる」が一番有用な情報だったのですが、PyInstaller関係のトラブル全般について書かれていることと、タイトルが私の欲しい情報とはまったく関係なかったのでなかなか見つけられませんでした。
かなり下の方の「subprocessモジュールを組み込んだアプリの --noconsole」の部分に書かれています。
他にもいろいろ読みましたが、解決に結びつけられるものはありませんでした。
解決方法としては
↓のソースをコピペ(「subprocess_args」の定義を追加)して
def subprocess_args(include_stdout=True):
# The following is true only on Windows.
if hasattr(subprocess, 'STARTUPINFO'):
# Windowsでは、PyInstallerから「--noconsole」オプションを指定して実行すると、
# サブプロセス呼び出しはデフォルトでコマンドウィンドウをポップアップします。
# この動作を回避しましょう。
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# Windowsはデフォルトではパスを検索しません。環境変数を渡してください。
env = os.environ
else:
si = None
env = None
# subprocess.check_output()では、「stdout」を指定できません。
#
# Traceback (most recent call last):
# File "test_subprocess.py", line 58, in <module>
# **subprocess_args(stdout=None))
# File "C:Python27libsubprocess.py", line 567, in check_output
# raise ValueError('stdout argument not allowed, it will be overridden.')
# ValueError: stdout argument not allowed, it will be overridden.
#
# したがって、必要な場合にのみ追加してください。
if include_stdout:
ret = {'stdout': subprocess.PIPE}
else:
ret = {}
# Windowsでは、「--noconsole」オプションを使用してPyInstallerによって
# 生成されたバイナリからこれを実行するには、
# OSError例外「[エラー6]ハンドルが無効です」を回避するために
# すべて(stdin、stdout、stderr)をリダイレクトする必要があります。
ret.update({'stdin': subprocess.PIPE,
'stderr': subprocess.PIPE,
'startupinfo': si,
'env': env })
return ret
(↑コメントの日本語はほぼ機械翻訳)
subbrocessの実行時に
try:
pcor = subprocess.run(コマンド文字列, **subprocess_args(True))
except (subprocess.CalledProcessError, IndexError, OSError):
pass
または
try:
pcor = subprocess.check_output(コマンド文字列, **subprocess_args(False))
except (subprocess.CalledProcessError, IndexError, OSError):
pass
みたいな感じで呼び出すことで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関係について、以下にたくさん書きました。
何かの問題の解決のヒントになるかもしれません。是非読んでみてください。
以上。
後記
最後までお読みくださりありがとうございます。
こんなに更新したのに、現在はほぼ誰も見ていないゴミカスクズ無価値ブログです。
よければ(はてな)ブックマークや拡散をお願いいたします。
更新の原動力として励ましが欲しいのです…<(_ _)>
ディスカッション
コメント一覧
はじめまして。自分も同じ要因で悩んでいたため、参考になりました。
pcor = subprocess.check_output(コマンド文字列, **subprocess_args(False))
上記の部分なのですが、subprocess_argsに渡す引数をTrueにすることで、subprocess.runでも動作しました。
もしcheck_outputに変更してしまったようでしたら、runに戻すことも可能になりますので、ご検討ください。
きし様、コメントありがとうございます。
こちらこそ貴重な情報ありがとうございます。
当方でも動作確認した上で、同じように悩んでいる方のためにも近日中に記載を追記・修正したいと思います。
はじめまして。自分もsubprocessが動かず悩んでいた所このサイトに救われました。
私はsubprocessでffmpegを動かそうとしていました。
subprocess.Popen(cmd, shell=True, **subprocess_args(True))
上のコメントを参考にPopenでも動作したということを書かせていただきます。(プログラミング初心者のため見当違いな報告かもしれませんが)
ありがとうございました。
ぽぽ様、コメントありがとうございます。
当サイトの情報がお役に立てたのであれば幸いです。
(私もどこかからもらっている情報がほとんどですのでできるだけ返したいと考えており)
> Popenでも動作した
フィードバックもありがとうございます。