【Python】「__main.py__」とは何か？このファイルから他のモジュールを実行する方法

# 概要

前回、`if __name__ == '__main__':`ブロックについて解説しました。
[【Python】「if __name__ == '__main__':」ブロックの意味とは？スクリプト実行とモジュール読み込み](https://qiita.com/Ryo-0131/items/68b3eaff7f0653457437)

この`__main__`に似た概念として、`__main__.py`という特別なファイルがあるので、これも一緒に整理してみました。

## 前提

ディレクトリ構造は以下の通りです。

```
my_package
├── __init__.py
├── __main__.py
└── module_example.py
```

`__main__.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`)を読み込むためには、以下のように実行します。

```__main__.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__`は、現在実行されているスクリプトファイルの名前を表示する特別な変数です。

ちなみに、以下でも同じですが、こちらは古い書き方です。

```py
    current_directory = os.path.dirname(os.path.abspath(__file__))
```

以前のdjangoの`settings.py`の`BASE_DIR`を見ると、上記のように書いてありますが、Django3.1以降の`settings.py`を見ると、`pathlib`の方になっているようです。
参考：[【Django】settings.py内のBASE_DIRの意味と指す場所を解説](https://office54.net/python/settings-base-dir)

Django3.1以降では、`pathlib`モジュールを利用しており、上述の通り、より直感的でオブジェクト指向な方法になっているんですね。

どちらも相対パス、絶対パスを出すことができます。以下の記事に詳細ありましたのでご参考までに。
[実行しているファイルが格納されているディレクトリのパスを取得したい。
](https://qiita.com/neko_the_shadow/items/09ff3a423954a2adfe18)

上記ディレクトリパス取得後に、`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
```

以下のように例外処理を入れると、このエラーをキャッチして処理することができます。

```py
    try:
        subprocess.run([sys.executable, module_example_path], check=True)
    except subprocess.CalledProcessError as e:
        print("CalledProcessErrorです")
```