-
-
Notifications
You must be signed in to change notification settings - Fork 910
Description
概要
pyxel run および pyxel watch コマンドで、サブディレクトリを指定してスクリプトを実行した際(例: pyxel run game/start.py)、そのスクリプトと同じディレクトリにあるモジュールのインポート(例: import hoge)に失敗する問題を報告します。
これは、pyxel run が検索パスを「相対パス」のまま登録しているため、pyxel.init() によるディレクトリ移動(os.chdir)が発生すると、登録されたパスが実在しない場所を指してしまうことが原因です。
再現用ファイル構成
以下のように、game というサブディレクトリの中にメインスクリプトと自作モジュールがある構成を想定します。
project/ (ここからコマンドを実行)
└── game/
├── start.py (import hoge; pyxel.init(...) を含む)
└── hoge.py (自作モジュール)
再現手順
project/ディレクトリ(game/の親ディレクトリ)へ移動します。- 以下のコマンドを実行します。
pyxel run game/start.py
期待される挙動
標準の python game/start.py コマンドと同様に、隣接する hoge.py が正常にインポートされ、ゲームが起動すること。
実際の挙動
ModuleNotFoundError: No module named 'hoge' が発生し、実行に失敗します。
原因の分析
本現象は、pyxel/cli.py 内のパス処理と pyxel.init() の仕様が干渉することで発生しています。
-
標準の
pythonコマンドの挙動との違い
標準のpython game/start.pyを実行した場合、Pythonインタプリタは自動的にスクリプトがあるディレクトリ(game/)の 絶対パス をsys.path[0]に挿入します。そのため、実行中にos.chdir()が呼ばれてもインポートの参照先は影響を受けません。 -
pyxel runによる相対パスの登録
pyxel/cli.pyのrun_python_script関数内では、sys.path.append(os.path.dirname(python_script_file))と処理されています。このため、コマンドライン引数に基づいた 相対パス(例: "game") がsys.pathの末尾に追加されます。 -
os.chdir()による参照切れ
start.py内でpyxel.init()が呼ばれると、カレントディレクトリがスクリプトの場所(project/game/)へ移動します。この移動後もsys.pathには起動時の相対パス("game")が残っているため、Pythonはproject/game/game/を探しに行ってしまい、インポートに失敗します。
※pyxel playは一時フォルダを絶対パスで扱うためこの問題は発生しませんが、runとwatchはユーザーの入力を直接利用するため発生します。
修正案の提案
pyxel/cli.py 内の run_python_script 等において、追加するディレクトリを 絶対パス に変換し、かつ標準の挙動に合わせて sys.path の先頭(index 0) に挿入することを提案します。
# 修正案 (pyxel/cli.py)
# os.path.abspath を使用して、ディレクトリ移動の影響を受けないようにする
script_dir = os.path.abspath(os.path.dirname(python_script_file))
sys.path.insert(0, script_dir)