作成したpythonのスクリプトをチームで共有するため、PyInstallerでexeを作成してみましたが、実行時にパスがうまく取得できないケースがありました。
原因
原因はスクリプトのファイルパスを取得する際に以下のpath1の記載を行っていたことで、path2の書き方に変更することで解消しました。
path1 = os.path.abspath(__file__)
path2 = os.path.abspath(sys.argv[0])
os.path.abspath は絶対パスを取得しています。path1の書き方をしているケースは多いかと思います。
動作確認
os.path.abspath の動作について確認してみます。環境は以下のとおりです。
- Windows 10
- Python 3.6.0
- PyInstaller 3.6
pythonファイルの実行
以下の構成でプログラムを作成しました。
test
├── modules
│ └── test2.py
└── test1.py
test1.py、test2.pyの内容はそれぞれ以下のとおりです。両方のファイルでパス取得をしています。test1.pyにtest2.pyをインポートして関数を呼び出しています。
# test1.py
import os
import sys
import modules.test2 as t2
path1 = os.path.abspath(__file__)
path2 = os.path.abspath(sys.argv[0])
print('test2.py __file__ :', path1)
print('test2.py sys.argv[0]:', path2)
t2.print_path()
# test2.py
import os
import sys
def print_path():
path1 = os.path.abspath(__file__)
path2 = os.path.abspath(sys.argv[0])
print('test2.py __file__ :', path1)
print('test2.py sys.argv[0]:', path2)
test1.pyの実行結果は以下のとおりです。
D:\test>python test1.py
test1.py __file__ : D:\test\test1.py
test1.py sys.argv[0]: D:\test\test1.py
test2.py __file__ : D:\test\modules\test2.py
test2.py sys.argv[0]: D:\test\test1.py
os.path.abspath の引数が __file__ の場合、各pythonファイルの絶対パスが取得されています。一方、引数が sys.argv[0] の場合、実行ファイルの絶対パスが取得されています。
exeファイルの実行
続いて、exeファイルを作成し、実行してみます。
まず、PyInstallerをインストールします。
pip install pyinstaller
exeファイルを作成します。–onefile を付与することで、関連ファイルを1ファイルにまとめることができます。
D:\test>pyinstaller test1.py --onefile
distフォルダの下にexeファイルが作成されます。exeファイルを実行します。
D:\test\dist>test1.exe
test1.py __file__ : D:\test\dist\test1.py
test1.py sys.argv[0]: D:\test\dist\test1.exe
test2.py __file__ : C:\Users\name\AppData\Local\Temp\_MEI18322\modules\test2.pyc
test2.py sys.argv[0]: D:\test\dist\test1.exe
pythonファイル実行時とは異なる結果になりました。
引数が sys.argv[0] の場合、test1.pyでもtest2.pyでもexeファイルの絶対パスが取得されています。
一方、引数が __file__ の場合、test1.pyでは、ディレクトリはカレントディレクトリ(D:\test\dist)、ファイル名がtest.pyとなっています。test2.pyでは、exe実行時にスクリプトが展開される一時フォルダになっています。
sys.argv[0] を指定することでexeファイルのパス取得が可能です。
モジュール上(test2.py)では、そもそも os.path.abspath でのパスの判定はせず、パスが必要な場合は、呼び出し元ファイル(test1.py)でパスを判定して引数として渡すといった形式にした方がいいように思えました。
まとめ
pythonファイルからexeファイルを作成する場合、実行ファイルパスの取得方法は要注意です。exeファイルのパスを取得する場合は、os.path.abspath(sys.argv[0]) とする必要があります。