とりゅふの森

GCPデータエンジニアとして生きる

【Python入門】if __name__ == '__main__'ってなに?

f:id:true-fly:20210727234443p:plain

こんにちは。今回はPythonの言語仕様的なお話です。
JavaやPHPに詳しいけど、Pythonはそこまで詳しくはないってエンジニアが、Pythonを書き始めるのって、そこまで苦ではないんですよね。かくゆう私も、最初はJavaやC#を学んで、この業界に入りました。
で、そんなJavaエンジニアだったり、新人の後輩によく聞かれていたのが、

「このソースの一番下にある…if __name__ == '__main__'ってなに?」

でした。そして僕はこう答えるのです。

「それはおまじないだよ、Javaでいうpublic void static main(String args[])だよ」

と流していたのでした。 今回はこのPythonのおまじないに真剣に向き合いたいと思います。

おまじないの正体とPythonのimportの仕組み

早速ですがこのおまじないの正体です。
if __name__ == '__main__'は、Pythonがpython [ファイル名].pyのように、コマンドラインから実行されたときのみ動く処理を書くときに用いる書き方です。

なのでコマンドラインから起動したい処理はこのif内に書いておけばいいわけですね。
でも、プログラムは上から下まで処理されるんだから、わざわざ明示的に書かなくてもよくない?って思いますよね。
実はこれ、Pythonのimportの仕組みと関係しているのです。
Pythonのimportは、importしたファイルを単に実行しているだけなのです。
Pythonは、ファイルをimportする時に実行することで、変数、メソッド、クラスなどをメモリ上に展開して、他のソースでも使えるようにしているんですね。
つまり裏を返せば、Pythonのソースにimport時に実行されるとまずい処理は、このif __name__ == '__main__'内に書かなくてはならないのです。importはコマンドライン実行ではないので、if文内の処理は実行されません。
試しに以下の2つのソースを用意し、main.pyをコマンドラインで実行してみます。
main.py

import sub

if __name__ == '__main__':
    #sub.py内のメソッドを呼び出す
    sub.run()

sub.py

def run():
    print("sub.pyの処理を実行")

# デバッグ用
run()
sub.pyの処理を実行
sub.pyの処理を実行

run()が2回走りましたね?
そうです、この結果の1行目は、import時にsub.pyの5行目の処理が実行され、2行目の処理は、main.pyの5行目で呼び出しされた結果です。
import=実行であることがわかりましたね。sub.pyを以下のように修正して、再度main.pyをコマンドラインで実行してみます。

def run():
    print("sub.pyの処理を実行")

if __name__ == '__main__':
    # デバッグ用
    run()
sub.pyの処理を実行

importするファイルにメソッドの実行を書く危険性

実はこれ結構危ないハマりどころかもしれません。
今回のsub.pyのように、メソッドの実行をベタ書きしてしまうと、importされるたびに、メソッドが実行されてしまいます。これによって、意図せぬ処理が走ってしまうんですね。
本当は認証の処理が別のメソッド内で走るのに、実際のロジック実行の処理が先に走ってしまい、エラーを発生させてしまったりと、バグや障害の発生源にもなりうるので、コマンドライン上でのみ実行すると決まりきっている処理は、
if __name__ == '__main__'内に書くのを推奨します。

nameの正体

ではおまじないの正体がわかったところで、最後に__name__の正体を見ていきましょう。この変数は、Pythonが予め用意しているグローバル変数になります。__name__のほかにも、__file__や、__package__などがあります。
この__name__には、コマンドラインから実行されたときは"main"が、importされた場合はモジュール名が設定されます。だから、if __name__ == '__main__'という構文で、コマンドラインから実行したか否かを判別できるんですね。実験として以下の3つのソースを用意し、main_module.pyをコマンドラインで実行してみます。
main_module.py

import sub_module
from  src import src_module

if __name__ == '__main__':
    print('main_module')
    print(__name__)
    print('sub_module')
    sub_module.run()
    print('src.src_module')
    src_module.run()

sub_module.py

def run():
    print(__name__)

src/src_module.py

def run():
    print(__name__)
main_module
__main__
sub_module
sub_module
src.src_module
src.src_module

ご覧の通り、main_module.py__name__には"main"が、importされた2ファイルは、それぞれモジュール名がセットされているのがわかります。ディレクトリを切っても、その階層まで含んでくれています。

まとめ

今回はif __name__ == '__main__'の正体と、Pythonのimportについて検証してみました。
importによる実行によって予期せぬ処理を実行してしまわぬよう、if __name__ == '__main__'を活用して、楽しいPythonライフを送りましょうー!