【簡単Python】pywebview exe化時の一時ディレクトリ問題を回避するコード例

アフィリエイト広告を利用しています。
QRコード
【QRコード】PC<--->スマホの切り替えにご利用ください
運営者・ポテ

いつもありがとうございます!

ノンプログラマー向け「Python解説シリーズ」へようこそ。

本稿では、「pywebview アプリを PyInstaller で exe 化した際に発生する一時ディレクトリ(_MEIPASS)パス問題と、その回避方法」を解説します。

pywebview に関する概要は、前回の記事で紹介しています。詳細は以下のリンクからご覧ください。

PyInstaller は、Python プログラムと必要なライブラリやリソースをまとめ、Python がインストールされていない環境でも実行できる exe ファイルとして出力できる便利なツールです。しかし、実行時にはユーザーには見えない一時ディレクトリ(_MEIPASS)に展開してから動くため、相対パスで指定したファイルが見つからないという問題が起きます。つまり、カレントディレクトリを基準に、相対パスで指定しているファイルがある場合、そのファイルを読み込む際にエラーが発生します。

この問題を回避するには、実行時に作業ディレクトリを exe ファイルのある場所に設定し、そこからファイルを確実に参照できるようにする必要があります。本稿では、その具体的なコード例と解説を紹介します。

Python を活用して、自身やコミュニティに適した改善ツールを作成し、仕事量は半分に、成果は 2 倍にしていきましょう。初心者の方にも理解しやすいよう、分かりやすく解説していきます。ぜひ、ご覧ください。

pywebview+PyInstaller|exe化で起きるパスエラーとは?

まず、exe化で起こるパスエラーを確認しましょう。

前回の記事で紹介したコードを再掲します。このコードをそのまま PyInstaller で exe 化すると、上述のエラーが発生します。

【ディレクトリ構成】

task_app(任意のディレクトリ名)/
    ├── .idea/           # 開発環境の設定フォルダ(PyCharmにより自動作成)
    ├── .venv/           # 仮想環境設定フォルダ(PyCharmにより自動作成)
    ├── index.html       # アプリ画面を作成する HTML ファイル
    ├── main.py          # アプリ全体を実行する Python スクリプト
    └── tasks.json       # タスクを保存するためのデータファイル(JSON形式)

【コード】

# 外部ライブラリ
import webview

# 標準ライブラリ
import json
import os

class TaskAPI:
    def __init__(self, storage_path="tasks.json"):
        self.storage_path = storage_path
        self.tasks = self._load_tasks()

    # ---------- 内部ユーティリティ ----------
    def _load_tasks(self):
        """
        保存ファイルからタスク一覧を読み込む

        :return: 読み込んだタスクリスト(ファイルがない場合は空のリスト)
        """
        if os.path.exists(self.storage_path):
            with open(self.storage_path, "r", encoding="utf-8") as f:
                return json.load(f)
        return []

    def _save_tasks(self):
        """
        タスク一覧をJSONファイルに保存する

        :return: なし(処理が成功すればファイルが上書き保存される)
        """
        with open(self.storage_path, "w", encoding="utf-8") as f:
            json.dump(self.tasks, f, ensure_ascii=False, indent=2)

    # ---------- 外部操作用API(JSから呼ばれるメソッド) ----------
    def add_task(self, task: str) -> bool:
        """
        タスクを追加して保存する

        :param task: 追加するタスクの文字列
        :return: True ※処理完了をJS側に伝える役割(値自体は使わない)
        """
        task = task.strip()
        if task:
            self.tasks.append(task)
            self._save_tasks()
        return True  # JS側に処理完了を伝えるためのTrue(値自体は使わない)

    def get_tasks(self):
        """
        登録済みのタスクの一覧を返す

        :return: 現在保存されているタスクのリスト
        """
        return self.tasks

    def remove_task(self, index: int) -> bool:
        """
        受け取った index のタスクを削除

        :param index: 削除対象となるタスクのインデックス(0始まり)
        :return: 削除に成功した場合は True、指定が不正な場合は False
        """
        try:
            self.tasks.pop(index)
            self._save_tasks()
            return True
        except IndexError:
            return False  # 念のため

# ---------- PyWebview 起動 ----------
if __name__ == "__main__":
    api = TaskAPI()
    window = webview.create_window(
        "タスク管理アプリケーション",
        "index.html",
        js_api=api,
        width=680,
        height=600
    )
    webview.start(debug=False)

【exe化】

pyinstaller --onefile --noconsole main.py
Information

仮想環境を作成してコマンドプロンプトから実行するか、PyCharm のターミナルで上記のコマンドを実行します。

【エラーメッセージ】

main.exe を実行すると、以下のメッセージが返ります。これは、指定されたファイル(index.html)が見つかりませんというエラーです。実際には、ファイル(index.html)は存在していますが、カレントディレクトリが一時ディレクトリに(_MEIPASS)になっているために起こります。

pywebview+PyInstaller|exe化で起きるパスエラーの直し方

このエラーを回避するコードを以下に示します。

# 外部ライブラリ
import webview

# 標準ライブラリ
import json
import os
import sys
from pathlib import Path


class TaskAPI:
    def __init__(self, storage_path="tasks.json"):
        self.storage_path = storage_path
        self.tasks = self._load_tasks()

    # ---------- 内部ユーティリティ ----------
    def _load_tasks(self):
        """
        保存ファイルからタスク一覧を読み込む

        :return: 読み込んだタスクリスト(ファイルがない場合は空のリスト)
        """
        if os.path.exists(self.storage_path):
            with open(self.storage_path, "r", encoding="utf-8") as f:
                return json.load(f)
        return []

    def _save_tasks(self):
        """
        タスク一覧をJSONファイルに保存する

        :return: なし(処理が成功すればファイルが上書き保存される)
        """
        with open(self.storage_path, "w", encoding="utf-8") as f:
            json.dump(self.tasks, f, ensure_ascii=False, indent=2)


    # ---------- 外部操作用API(JSから呼ばれるメソッド) ----------
    def add_task(self, task: str) -> bool:
        """
        タスクを追加して保存する

        :param task: 追加するタスクの文字列
        :return: True ※処理完了をJS側に伝える役割(値自体は使わない)
        """
        task = task.strip()
        if task:
            self.tasks.append(task)
            self._save_tasks()
        return True  # JS側に処理完了を伝えるためのTrue(値自体は使わない)

    def get_tasks(self):
        """
        登録済みのタスクの一覧を返す

        :return: 現在保存されているタスクのリスト
        """
        return self.tasks

    def remove_task(self, index: int) -> bool:
        """
        受け取った index のタスクを削除

        :param index: 削除対象となるタスクのインデックス(0始まり)
        :return: 削除に成功した場合は True、指定が不正な場合は False
        """
        try:
            self.tasks.pop(index)
            self._save_tasks()
            return True
        except IndexError:
            return False  # 念のため


# ---- ユーティリティ関数 ----
def set_workdir():
    if getattr(sys, 'frozen', False):
        cur_dir = os.path.dirname(sys.executable)
        os.chdir(cur_dir)
    else:
        cur_dir = os.path.dirname(os.path.abspath(__file__))
        os.chdir(cur_dir)


# ---------- PyWebview 起動 ----------
if __name__ == "__main__":
    set_workdir()

    # 絶対パスを file:// に変換して内部HTTP経由(http://~)による404を避ける
    index_url = Path("index.html").resolve().as_uri()

    api = TaskAPI()
    window = webview.create_window(
        "タスク管理アプリケーション",
        index_url,
        js_api=api,
        width=680,
        height=600
    )
    webview.start(debug=False)
運営者・ポテ

解説していきます!

exe 化するために改修した部分を解説していきます。

まずは、このセクションです。

# ---- ユーティリティ関数 ----
def set_workdir():
    if getattr(sys, 'frozen', False):
        cur_dir = os.path.dirname(sys.executable)
        os.chdir(cur_dir)
    else:
        cur_dir = os.path.dirname(os.path.abspath(__file__))
        os.chdir(cur_dir)

ここでは、実行時の作業ディレクトリを適切に設定するための関数 set_workdir() を定義しています。

PyInstaller で exe 化した場合と、通常の Python スクリプトとして実行する場合とで処理を分け、どちらの環境でもファイルを正しく参照できるようにしています。

exe 化されている場合は sys.frozenTrue となり、sys.executable から実行ファイルのパスを取得して、そのディレクトリをカレントディレクトリに設定します。
通常のスクリプト実行時には __file__ からスクリプトの絶対パスを求め、そのディレクトリをカレントディレクトリとします。

この処理によって、相対パスで指定した HTML や JSON などのファイルが、実行環境に依存せず確実に読み込まれるようになります。

# ---------- PyWebview 起動 ----------
if __name__ == "__main__":
    set_workdir()

    # 絶対パスを file:// に変換して内部HTTP経由(http://~)による404を避ける
    index_url = Path("index.html").resolve().as_uri()

    api = TaskAPI()
    window = webview.create_window(
        "タスク管理アプリケーション",
        index_url,
        js_api=api,
        width=680,
        height=600
    )
    webview.start(debug=False)

ここでは、pywebview アプリを起動するための処理を記述しています。

最初に set_workdir() を呼び出し、作業ディレクトリを exe ファイルの場所に設定します。

続いて、index.html の絶対パスを file:// 形式に変換しています。これにより、内部 HTTP 経由でアクセスした場合に発生する 404 エラーを回避できます。この URL は、ウィンドウを生成する際に「表示するコンテンツ」として指定します。

次に、TaskAPI のインスタンスを生成し、webview.create_window() でアプリのウィンドウを作成します。ここでは、タイトル、表示する HTML ファイルの URL、JavaScript との連携用 API、ウィンドウサイズを指定しています。

最後に webview.start() を呼び出して、GUI アプリケーションを起動します。

運営者・ポテ

以上で解説は終了です。ありがとうございました。


Python プログラミングスキルアップのための参考情報

ここでは参考図書を紹介いたしますが、これに限らず自分に合うものを選ぶことが重要だと考えております。皆様の、より一層のご成功を心からお祈りしております。


「独学プログラマー」というPythonを題材にした書籍は大変勉強になりました。Pythonの技術解説だけにとどまらず、プログラミングの魅力や基本的な知識、思考法、仕事の進め方まで幅広く学べます。


こちらの記事でも紹介しております。もしよろしければご覧ください。


また、Pythonに関する書籍は多数出版されています。興味のある方は、チェックしてみてください。

\チェックしてみよう/

\チェックしてみよう/

\チェックしてみよう/

QRコード
【QRコード】PC<--->スマホの切り替えにご利用ください

【初心者歓迎】【無料相談受付中】業務改善アプリケーション作成のご相談を承ります

運営者・ポテ

いつもありがとうございます!

限られた時間をより良く使い、日本の生産性を高めたい──

みんなの実用学を運営するソフトデザイン工房では、業務整理や業務改善アプリケーション作成のご相談を承っております。

お気軽にご相談ください。


こちらの記事でも紹介しております。

おわりに

運営者・ポテ

ご覧いただきありがとうございました!

本稿では、「pywebview アプリを PyInstaller で exe 化した際に発生する一時ディレクトリ(_MEIPASS)パス問題と、その回避方法」を解説いたしました。

お問い合わせやご要望等ございましたら、「お問い合わせ/ご要望」またはコメントにて、ご連絡いただければ幸いでございます。

皆様の人生がより一層素晴らしいものになるよう、少しでもお役に立てれば幸いでございます。

なお、当サイトでは様々な情報を発信しております。もしよろしければ、トップページもご覧いただけると幸いでございます。

この記事を書いた人

運営者・ポテソフトデザイン工房|日々の業務にちょうどいい自動化を
■人生を追求する凡人 ■日本一安全で、気の向くままに自分の時間を過ごせる、こだわりのキャンプ場を作るのが夢 ■ソフトデザイン工房代表(個人事業者) - 業務設計&業務支援アプリケーション作成サービスを展開 ■人生は時間そのもの。ひとりでも多くの人が「より良い人生にするために時間を使って欲しい」と願い、仕事のスキルの向上、余暇の充実、資産形成を研究。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です