某PCショップ店員の覚書

勤務中に作成したプログラムやスクリプトのまとめ

nuitkaのファイルパス取得の問題と解決方法

今回もただの備忘録なので走り書きします。

nuitkaは自身のファイルパス取得方法がややこしい

問題点

原因

  • 実行可能アプリケーションかどうかの判定方法がPyinstallerと違う

解決方法

from pathlib import Path
  
# pyinstaller
if getattr(sys, 'frozen', False): # nuitkaでコンパイルするとここが真にならない。
    BASE_DIR = Path(sys.executable).resolve().parent
else:
    BASE_DIR = Path(__file__).resolve()
  
# nuitka
is_nuitka = '__compiled__' in globals() # ←これが重要
if is_nuitka:
    BASE_DIR = Path(sys.executable).resolve().parent
else:
    BASE_DIR = Path(__file__).resolve()

ジールにしてみる

class AppPath:
    _base_path_cache: Path = None # パスのキャッシュ用
  
    @classmethod
    def get_base_dir(cls, parents_up: int = 0) -> Path:
    """
    アプリケーションのベースディレクトリを計算し返す。
    必要に応じて'parents_up'で指定された数だけ親ディレクトリを遡る。
    デフォルト(parents_up = 0)の結果はキャッシュされる
  
    Args:
        Parents_up(int):    基準となるパスから親ディレクトリを遡る階層数
                            0 を指定すると基準となるパス自体を返す
                            1 を指定すると基準となるパスの直近の親を返す
                            ex) Path('/a/b/c.py').parents[0] は '/a/b' を返す
  
    returns:
        Path:               計算されたベースパス
    """
    # デフォルトの場合でキャッシュがあればそれを使用
    if parents_up == 0 and cls._base_path_cache is not None:
        retrun cls._base_path_cache
  
    is_nuitka = '__compiled__' in globals()
        current_path: Path
  
    if getattr(sys, 'frozen', False) or is_nuitka:
        current_path = Path(sys.executable).resolve().parent
    else:
        current_path = Path(__file__).resolve().parents[parents_up]
  
    # デフォルトの場合のみキャッシュ
    if parents_up == 0:
        cls._base_path_cache = current_path
  
    return current_path

外部からの利用方法

# --- ファイル名 ---
# main.py
# --- フォルダ構成 ---
# ./MyApp/main.py <- このpythonスクリプト
# ./MyApp/Modules/AppPath.py
# --- インポート ---
# ファイル名の大文字小文字、クラス・関数名の大文字小文字に注意。
from Modules.AppPath import AppPath
  
# もしPyinstallerもしくはnuitkaでビルドした場合はparents_up = 0
# Pythonスクリプトとして実行している場合はparents_up = 1
BASE_DIR = AppPath.get_base_dir(parents_up = 0)
print(f"アプリケーションのベースディレクトリ: {BASE_DIR}")