【簡単Python】exe化するスクリプトは相対パスのコーディング方法に注意|'__file__' / os.getcwd()関数 / getattr関数



いつもありがとうございます!
「ノンプログラマー向けPython解説シリーズ」へようこそ。
Python で作成したプログラムを 、Python がインストールされていないPCで実行するにはプログラムを "exe化"する必要があります。
Python で効率化ツールなどを作成しても、普通の会社や研究室などでは自分以外の人全員のPCに Python がインストールされていることは稀だと思いますので、必然的に exe化 して配る必要が生じます。
Pythonスクリプト(.py)を直接実行する場合と異なり、exe化した実行ファイルを実行すると、実行ファイルが一時ディレクトリ(Temp)に展開され、これが問題となる場合があります。
以下に例を示します。
ディレクトリ構成
your_program/
  ├─ module.py
  └─ module.exe
コード
import os
# カレントディレクトリを取得
cur_dir = os.path.dirname(__file__)
print(cur_dir)
input('>>> Press Enter Key to Shutdown')実行結果
上述のコードを実行すると以下の結果が返ってきます。Pythonスクリプトと実行ファイル(.exe)とで、結果が異なります。
C:\Path\to\your_directory\your_program
>>> Press Enter Key to ShutdownC:\Path\to\your_directory\Temp\_MEI87762    # 一時ディレクトリに展開されている
>>> Press Enter Key to Shutdown.exe で実行した場合、カレントディレクトリが一時ディレクトリになっていることが分かります。
これを想定せずにコードの中に何らかの相対パスの記述をしているとエラーが発生します。相対パスの基点となるディレクトリが意図せず変わってしまうためです。
具体例を示します。以下のようなコードで、main.exe を実行すると data ディレクトリにアクセスできないためエラーが発生し、処理が中断されます。(main.py は正常に動作します)
ディレクトリ構成
your_program/
├─ main.py
├─ main.exe
├─ src/
  └─ module.py
└─ data/
  └─ data.csv
コード
# 自作モジュールをインポート
from src import module
# 自作モジュールを実行
module.show_files()
input('>>> Press Enter Key to Shutdown')def show_files():
    # 標準ライブラリのインポート
    import os
    import glob
    # カレントディレクトリの取得
    cur_dir= os.path.dirname(os.path.abspath(__file__))
    os.chdir(cur_dir)
    # dataディレクトリにあるファイルを取得
    file = glob.glob('../data/*')   
    # ファイル名を出力
    print(os.path.basename(file[0]))
if __name__ == '__main__':
    show_file()実行結果
main.py は正常に動作しますが、main.exe はエラーが発生します。
data.csv
>>>press any key to shut downTraceback (most recent call last):
  File "main.py", line 3, in <module>
    module.show_files()
  File "src\module.py", line 9, in show_files
    os.chdir(cur_dir)
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。: 'C:\\Path\\to\\your_directory\\Temp\\_MEI6402\\src'本稿では、これの解決方法を解説します。
解決方法
os.getcwd関数を使用する方法

os.getcwd()は、現在の作業ディレクトリを返す関数です。
.exeを実行したとき、__file__はその.exeが展開された一時ディレクトリのパスを返します。一方、os.getcwd()は実行時の作業ディレクトリ、つまり.exeが置かれているディレクトリのパスを返します。

以下に、具体例を用いて解説していきます!
ディレクトリ構成
your_program/
├─ main.py
├─ main.exe
├─ src/
  └─ module.py
└─ data/
  └─ data.csv
コード
 # 自作モジュールのインポート
from src import module
 # 自作モジュールの実行
module.show_files()
input('>>>Press Enter key to Shutdown')
# 標準ライブラリのインポート
import os
def show_files():
    # 現在の作業ディレクトリを取得
    cur_dir = os.getcwd()
    # カレントディレクトリが 'src' で終わるかどうかをチェック
    if cur_dir.endswith('src'):
        
        # 'src' ディレクトリ内で実行されている場合、親ディレクトリの 'data' ディレクトリを参照
        entries = os.listdir('../data')
        
    else:
        
        # それ以外の場合、カレントディレクトリ内の 'data' ディレクトリを参照
        entries = os.listdir('data')
    # 'data' ディレクトリ内の各ファイル名を出力
    for entry in entries:
        print(entry)
# スクリプトが直接実行された場合にのみ、show_files() を呼び出す
if __name__ == '__main__':
    show_files()実行結果
main.py、main.exe 共に、正常に以下の結果が返ってきます。
data.csv
>>>Press Enter key to Shutdownまた、module.py 単体で実行しても以下の結果が返ってきます。
data.csvこのように、プログラムを作成する際は、module.py単独で実行したときも、main.py (main.exe)から実行した時も正常に動作させることが好ましいです。なぜならば、モジュール単体でテストを行うのに便利だからです。
注意点
os.getcwd()関数は、現在の作業ディレクトリを返す関数です。直接的に.exeを識別するものではありません。

以上で os.getcwd() を使う方法の解説は終了です!ありがとうございました!
getattr関数を使用する方法(推奨)

getattr関数は、オブジェクトから指定した属性を文字列として取り出すことができる関数です。
基本構文は以下の通りです。
getattr(object, name[, default])| 引数 | 解説 | 
|---|---|
| object | 属性を取得したい対象のオブジェクトを指定します。 | 
| name | 取得したい属性の名前を文字列で指定します。 | 
| default(省略可能) | 属性が存在しない場合に返されるデフォルト値を指定します。この引数を指定しない場合、属性が存在しないと AttributeErrorが発生します。 | 

以下に、具体例を用いて解説していきます!
ディレクトリ構成
your_program/
├─ main.py
├─ main.exe
├─ src/
  └─ module.py
└─ data/
  └─ data.csv
コード
 # 自作モジュールのインポート
from src import module
 # 自作モジュールの実行
module.show_files()
input('>>>Press Enter key to Shutdown')
# 標準ライブラリのインポート
import os
import sys
def show_files():
    # プログラムが実行ファイル(Frozenバンドル)として実行されている場合の処理
    if getattr(sys, 'frozen', False):
        # 実行ファイルのディレクトリを取得
        cur_dir = os.path.dirname(sys.executable)
        
        # 'src'ディレクトリに移動
        os.chdir(os.path.join(cur_dir, 'src'))
    else:
        # スクリプトファイルのディレクトリを取得
        cur_dir = os.path.dirname(os.path.abspath(__file__))
        # そのディレクトリに移動
        os.chdir(cur_dir)
    # '../data'ディレクトリ内のエントリ(ファイルやフォルダ)を取得
    entries = os.listdir('../data')
    # 各エントリ(フォルダ内のファイルやサブフォルダ)を表示
    for entry in entries:
        print(entry)
if __name__ == '__main__':
    show_files()
コード実行結果
main.py、main.exe 共に、正常に以下の結果が返ってきます。
data.csv
>>>Press Enter key to Shutdownまた、module.py 単体で実行しても以下の結果が返ってきます。
data.csv繰り返しになりますが、このように、module.py単独で実行したときも、main.py (main.exe)から実行した時も正常に動作させることが好ましいです。なぜならば、モジュール単体でテストを行うのに便利だからです。

以上でgetattrを使う方法の解説は終了です!ありがとうございました!
Python プログラミングスキルアップのための参考情報
ここでは参考図書を紹介いたしますが、これに限らず自分に合うものを選ぶことが重要だと考えております。皆様の、より一層のご成功を心からお祈りしております。
「独学プログラマー」というPythonを題材にした書籍は大変勉強になりました。Pythonの技術解説だけにとどまらず、プログラミングの魅力や基本的な知識、思考法、仕事の進め方まで幅広く学べます。
こちらの記事でも紹介しております。もしよろしければご覧ください。
また、Pythonに関する書籍は多数出版されています。興味のある方は、チェックしてみてください。
\チェックしてみよう/
\チェックしてみよう/
\チェックしてみよう/

おわりに


ご覧いただきありがとうございました!
今回の記事では、Python スクリプト(.py)を exe 化して実行する際に、実行ファイルが一時ディレクトリ(Temp)に展開されてしまうことによる弊害への対処方法を解説いたしました。
お問い合わせやご要望がございましたら、「お問い合わせ/ご要望」フォームまたはコメント欄よりお知らせください。
この記事が皆様のお役に立てれば幸いです。
なお、当サイトでは様々な情報を発信しております。よろしければトップページもあわせてご覧ください。
この記事を書いた人
- ソフトデザイン工房|日々の業務にちょうどいい自動化を
- ■人生を追求する凡人 ■日本一安全で、気の向くままに自分の時間を過ごせる、こだわりのキャンプ場を作るのが夢 ■ソフトデザイン工房運営(個人事業者) - 業務改善アプリケーションをご提供 ■人生は時間そのもの。ひとりでも多くの人が「より良い人生にするために時間を使って欲しい」と願い、仕事のスキルの向上、余暇の充実、資産形成にチャレンジ。





