LoginSignup
90
85

More than 5 years have passed since last update.

【補講】if __name__ == '__main__' の下にコードをダラダラと書く人、挙手しなさい

Last updated at Posted at 2017-03-09

みなさん、こんにちは。
if __name__ == '__main__' の下にコードをダラダラと書く人、挙手しなさいが「いいね」100を超えました、ありがとうございます。
先生、みんなに話しておきたい事があります。

Shadows name の恐ろしさ

みなさん、Shadows name警告を甘く見過ぎです。
グローバルスコープを汚染することによって引き起こされるShadows nameの恐ろしさを説明しましょう。

テキトウな例で申し訳ありませんが、以下のコードがあったとします。

import sys

def process(n):
    x = 0

    # ...なんかいろんな処理

    if x > 1:
        n *= 10

    # ...なんかいろんな処理

    return n


if __name__ == '__main__':
    for x in sys.argv[1:] or [1, 2, 3]:
        process(int(x))

ある事情により、process関数の始め方の処理が不要になりましたので、変数xをコメントアウトしました。
しかし中盤のコードに埋まっていた変数xを見落としてしまいました。

def process(n):
    # ここをコメントアウト
    # x = 0

    # ...なんかいろんな処理

    # !!!! コメントアウトしわすれた !!!!
    if x > 1:
        n *= 10

    # ...なんかいろんな処理

    return n

この場合、xがグローバル変数として参照されるのでエラーになりませんし、静的解析ツールにも検知されません。

Pythonにおける単純なミスは99%静的解析で検知できますが、Shadows nameはそれをすり抜けてしまう厄介なバグになります。
静的解析をすり抜けてしまうということは、コメントアウト1つするにも細心の注意を払う必要があります。

そこ、Python先生の言語仕様をディスるのはやめなさい。

もちろん、先ほどの例は極端なシチュエーションでしたが、起こる可能性は充分にあります。
こんな厄介なバグを引き起こすリスクを抱えるくらいならば、if __name__ == '__main__'の下は早々に関数で包んでしまった方が気が楽ですよね。

PyCharm先生の注意を無視してはいけません(追記)

実際の話、PyCharmのようにリアルタイムで静的解析してくれるツールでは、コードを書く途中で注意してくれるので、この問題はあまり起こらないかもしれません。

2017-03-10_13h07_48.png

しかし警告を無視していたりOFFにしていると、こうなります。
沈黙するPyCharm先生...

2017-03-10_13h08_07.png

Pythonの静的解析ツールは、時にわかりにくいバグも未然に防いでくれます。
その警告を埋もれさせないよう、コードの状態を常にグリーンにするよう心がけることが大切です。

if __name__ == '__main__'を書かないという人へ

そもそもif __name__ == '__main__'は、トップレベルのスクリプト環境の場合のみ実行したい処理を書く場所です。

では、トップレベルのスクリプトとは何かというと、Pythonから直接実行される"メインモジュール"の事です。
メインモジュール、または他のスクリプトからimportされるスクリプトを単に"モジュール"と呼びます。

メインモジュールとしてしか実行されないスクリプトには、基本的にif __name__ == '__main__は必要はありません。だって__name__には絶対に__main__しか入らないのですから、if文を書くだけ無駄ですよね。

「じゃあ、メインモジュールにif __name__ == '__main__なんか書かなくていいんじゃん」

と、なりますよね。

先生の意見は「メインモジュールにそれなりのコードを書くのであれば、if __name__ == '__main__を書くべき」です。

例えば、以下のようなメインモジュールがあったとします。

import sys


def main(args=sys.argv):
    # なんちゃらかんちゃら

    return 0

if __name__ == "__main__":  
    sys.exit(main())

典型的なメインモジュールの処理です。
if __name__ == '__main__がないメインモジュールは以下のようになります。

import sys


def main(args=sys.argv):
    # なんちゃらかんちゃら

    return 0


sys.exit(main())

この2つは同じ処理をしますが、2つ目のメインモジュールは困ることが1つだけあります。

他の人がモンキーパッチができない

ということです。

モンキーパッチとは?

モンキーパッチとは既存のライブラリの動作を乗っ取って、動的に書き換えてしまうテクニックです。

モンキーパッチ Wikipedia

モンキーパッチの実例は、先生の記事を参照してください。

Sphinxのドキュメント作成を便利にする「sphinx-quickstart-plus」を作りました

つまり、あなたが作ったスクリプトを他の誰かが拡張したいときに、if __name__ == '__main__がないと、モンキーパッチがしにくいコードになります。

以下のようなa.pyというメインモジュールがあったとしましよう。

a.py
import sys


def main(args=sys.argv):
    # なんちゃらかんちゃら

    return 0


sys.exit(main())

このa.main関数をモンキーパッチ(拡張)しようとしてインポートすると、プログラムが終了してしまいます。

monkey.py
import a

print("monkey patch")

a.main([])

ですので、メインモジュールでもif __name__ == '__main__を書いておいた方が無難(必須ではない)です。
ただし、サンプルコードや書捨てコードであればif __name__ == '__main__は不要です。

まとめ

先生の言いたいことは、この2つです。

  • グローバルスコープを汚すことは静的解析の信頼性を落とす行為、ギルティ!
  • メインモジュールでもif __name__ == '__main__を書いたほうが無難

以上です。

90
85
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
90
85