概要
前回、if __name__ == '__main__':ブロックについて解説しました。
【Python】「if name == 'main':」ブロックの意味とは?スクリプト実行とモジュール読み込み
この__main__に似た概念として、__main__.pyという特別なファイルがあるので、これも一緒に整理してみました。
前提
ディレクトリ構造は以下の通りです。
my_package
├── __init__.py
├── __main__.py
└── module_example.py
__main__.pyのコードは以下の通りです。
def main():
print("ここから始まるよ!!")
if __name__ == "__main__":
main()
上記のif __name__ == "__main__":って何してるの?という場合は、上述の記事をご参照ください。
__main__.pyとは何なのか?
コードの実行結果を見た方が早いと思うので、まずは以下コマンドを実行します。
python3 -m my_package
すると以下が出力されます。
ここから始まるよ!!
このことから、__main__.pyファイルは、Pythonパッケージを実行したときに呼び出される特別なファイル、ということがわかります。
つまり、「python -m パッケージ名」を実行するとそのパーケージの__main__.pyが呼ばれる、ということです。こういうのをパッケージのエントリーポイントと言います。プログラムの始まり、みたいなイメージですね。
一応、以下のコマンドでも実行はできます。
ただし、これは本来の使い方ではないので、望ましくないでしょう。
python3 my_package/__main__.py
__main__.pyから他のファイルを読み込むためには?
ちょっと応用編として。
__main.py__から他のファイル(module_example.py)を読み込むためには、以下のように実行します。
import subprocess
import os
from pathlib import Path
def main():
current_directory = Path(__file__).resolve().parent
module_example_path = os.path.join(current_directory, "module_example.py")
subprocess.run(["python", module_example_path], check=True)
if __name__ == "__main__":
main()
このコードでは、以下のことをしています。
Path(__file__).resolve().parentでディレクトリパスを取得
まず、Path(__file__).resolve().parentを使用して現在のスクリプトのディレクトリパスを取得しています。__file__は、現在実行されているスクリプトファイルの名前を表示する特別な変数です。
ちなみに、以下でも同じですが、こちらは古い書き方です。
current_directory = os.path.dirname(os.path.abspath(__file__))
以前のdjangoのsettings.pyのBASE_DIRを見ると、上記のように書いてありますが、Django3.1以降のsettings.pyを見ると、pathlibの方になっているようです。
参考:【Django】settings.py内のBASE_DIRの意味と指す場所を解説
Django3.1以降では、pathlibモジュールを利用しており、上述の通り、より直感的でオブジェクト指向な方法になっているんですね。
どちらも相対パス、絶対パスを出すことができます。以下の記事に詳細ありましたのでご参考までに。
実行しているファイルが格納されているディレクトリのパスを取得したい。
上記ディレクトリパス取得後に、os.path.join()を使ってmodule_example.pyのフルパスを構築しています。
subprocess.run()でコマンドを実行
そして、subprocess.run()を使用してmodule_example.pyを実行しています。
subprocess.run()は、外部プログラムやコマンドをPythonスクリプト内で実行するための方法。
こちらにも古いものがあります。commandsというモジュール。
Python3以降では削除されている模様。お気をつけて。
["python", module_example_path]は、実行するコマンドを指定しています。ここでは、Pythonのインタープリターにmodule_example.pyを実行するよう指示しています。
check=Trueは、サブプロセスが正常に完了しない場合にCalledProcessErrorを発生させることを意味します。つまり、もし実行されたサブプロセスがエラーで終了した場合、例外が発生し、プログラムが中断されます。
例えば、module_example.pyをhogehoge.py(存在しないファイル)にしたら、以下のエラーメッセージが表示されます。
subprocess.CalledProcessError: Command '['/Library/Developer/CommandLineTools/usr/bin/python3', '/Users/xxxxx/TEST/my_package/hogehoge.py']' returned non-zero exit status 2.
hogehoge.pyにした状態で、check=Falseもしくはcheckオプションを消すと、以下のような結果になります。
/Library/Developer/CommandLineTools/usr/bin/python3: can't open file '/Users/xxxxx/TEST/my_package/hogehoge.py': [Errno 2] No such file or directory
以下のように例外処理を入れると、このエラーをキャッチして処理することができます。
try:
subprocess.run([sys.executable, module_example_path], check=True)
except subprocess.CalledProcessError as e:
print("CalledProcessErrorです")