__file__ はスクリプトのパスを取得する便利な変数だが、すべての状況で使えるわけではない。使えないケースと代替手段を理解しておく必要がある。
__file__ が使えないケース
インタラクティブシェル(Python REPL)
`exec()` や `eval()` で実行されたコード
`-c` オプションでコマンドラインから実行した場合
PyInstaller などで exe 化した場合(frozen)
zip ファイルからインポートされたモジュール
インタラクティブシェルでの問題
# Python REPL で実行
>>> __file__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '__file__' is not defined
exec() での問題
code = '''
print(__file__)
'''
exec(code) # NameError: name '__file__' is not defined
PyInstaller で exe 化した場合
PyInstaller でパッケージングすると、__file__ は一時ディレクトリを指すことがある。
# exe 化されたプログラムでは
print(__file__) # C:\Users\...\AppData\Local\Temp\_MEI12345\script.py
実行ファイルの場所を取得するには sys.executable を使う。
import sys
import os
def get_app_path():
if getattr(sys, 'frozen', False):
# PyInstaller で frozen されている
return os.path.dirname(sys.executable)
else:
# 通常の Python スクリプト
return os.path.dirname(os.path.abspath(__file__))
sys.frozen 属性は PyInstaller によって設定される。
zipimport の場合
Python は zip ファイル内のモジュールをインポートできるが、その場合 __file__ は zip 内のパスを指す。
# module.py が archive.zip 内にある場合
import sys
sys.path.insert(0, 'archive.zip')
import module
print(module.__file__) # archive.zip/module.py
このパスは通常のファイル操作には使えない。
代替手段
inspect モジュールを使う
inspect モジュールは呼び出し元の情報を取得できる。
import inspect
def get_script_path():
frame = inspect.currentframe()
if frame is None:
return None
# 呼び出し元のフレームを取得
caller_frame = frame.f_back
if caller_frame is None:
return None
return caller_frame.f_code.co_filename
sys.argv[0] を使う
コマンドラインから実行された場合、sys.argv[0] にスクリプトのパスが入る。
import sys
import os
script_path = os.path.abspath(sys.argv[0])
script_dir = os.path.dirname(script_path)
ただし、python -m module で実行した場合や、インポートされた場合は期待通りに動かない。
importlib.resources を使う
パッケージ内のリソースファイルにアクセスする場合は importlib.resources が最も信頼性が高い。
# Python 3.9 以降
from importlib.resources import files
# パッケージ内のファイルを取得
config_text = files('mypackage').joinpath('config.json').read_text()
# パスが必要な場合
from importlib.resources import as_file
with as_file(files('mypackage').joinpath('data.bin')) as path:
# path は一時ファイルのパス(zip 内でも動作)
with open(path, 'rb') as f:
data = f.read()
堅牢な実装例
様々な状況に対応する関数の例を示す。
import sys
import os
def get_base_path():
"""スクリプトまたは実行ファイルのディレクトリを取得"""
# PyInstaller で frozen されている場合
if getattr(sys, 'frozen', False):
return os.path.dirname(sys.executable)
# __file__ が利用可能な場合
if '__file__' in globals():
return os.path.dirname(os.path.abspath(__file__))
# sys.argv[0] にフォールバック
if sys.argv[0]:
return os.path.dirname(os.path.abspath(sys.argv[0]))
# 最終手段:カレントディレクトリ
return os.getcwd()