「1日1python」のお時間です。

めっちゃ詰まってたところがようやく融けたのでサマリ兼パイソニスタ仲間のためのノートを書く。今回実現したいのは以下のような条件だ。

  • ディレクトリ構成はこう。
トップディレクトリ
    ├─ a.exeのショートカット
    └─ data
        ├─ a.exe(pythonスクリプトをcx_freezeでexe化したもの)
        └─ image
            └─ なんか適当な画像
  • これをセットとして配布したい。ユーザにはショートカットからdataディレクトリのa.exeを起動してもらう。
  • 配布するので、ショートカットには絶対パスを使えない(相対パスを使わないといけない)。
  • a.exeはimageフォルダの中から画像を読んで使う。その際、画像のパスはすべてa.exeから見た相対パスになってる。

次のような問題が発生した。

  • そもそもショートカットに相対パスどう書くのかわかんねえ。
  • ショートカットからa.exeを実行するとカレントディレクトリがトップディレクトリになっちゃう。そのためa.exeは自分をカレントディレクトリとした相対パスでは画像に辿りつけない。

で、次のように解決した。

  • ショートカットに相対パス仕様にする方法は以下。
    • 「リンク先」を %windir%\explorer.exe ".\data\a.exe" にして、
    • 「作業フォルダー」を空欄にする。
  • ショートカットから実行されたときはカレントディレクトリをdataディレクトリに移す。
    • ショートカットから実行されたかどうかは、まあたとえば「os.path.exists('./image')がFalseならショートカットからの実行だ」みたいな感じで適当に。
    • ディレクトリの移動はこう。 os.chdir(os.path.dirname(sys.executable)) もちろんosとsysはimportしておく。

ディレクトリ移動のとき使っている sys.executable は、ショートカットから実行していようがなんだろうが、最終的に実行されているexeファイルのパスを取得してくれるマジの優れもの。今回はじめて知った。a.pyスクリプトをそのまま実行すればC:/Python34/python.exeとか表示してくれるし、exe化してa.exeにすればa.exeのパスを教えてくれる。これはこの先も本当に世話になりそうだ。

ショートカットに相対パスを書く方法なんてのは情報がたくさんあってさらっと分かったのだけれど、ショートカット先のexeファイルから更に相対パスを使う、ってとこまでの記述が全然みつからなくてなあ。最初に考えたのは、「ショートカットから実行されたときは os.getcwd() + '/data/data/image/画像' で画像を読み込む」っつー泥臭い手だった。妙案だと思ったんだけど、これは「%windir%\explorer.exe ".\data\a.exe"というショートカットから実行したa.exeのパスはC:/windows/system32/a.exeになる」という驚きの仕様によって粉砕された。アタマに'data/'をつけたところで、「C:/windows/system32/data/image/画像」なんてパスは存在しねーよってことになっちゃうわけ。windowsテメェ、こういう状況では実行したexeのパスが欲しいに決まってんだろ、空気読め、この野郎。

とまあその仕様のせいで、「カレントディレクトリを移動しちゃえばいんじゃね?」とひらめいてもカレントディレクトリをプログラムから求めることができずにいたのだが、sys.executable の発見に救われたって流れ。ディレクトリ移動の手法は上述の泥臭い手よりずっとスマートだ。ショートカットがどこにあってもプログラム側を変更する必要がないからね。