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

  • 156
    いいね
  • 10
    コメント

あけましておめでとうございます。
新年早々ですが、先生、みなさんに言いたいことがあります。

pythonコードで、if __name__ == '__main__'の下にコードをダラダラと書く人、挙手しなさい。

こんな感じに、if __name__ == '__main__'の下にコードを書く人です。

#! usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function, absolute_import
import sys


def say_args(kind, data_list):
    print("kind:", kind, " data:", data_list)


if __name__ == '__main__':
    args = sys.argv[1:]
    assert args

    kind = args[0].lower()
    data_list = [x.lower() for x in args[1:]]

    say_args(kind, data_list)

こういうコードを書いている人、先生、怒らないから挙手しなさい。

こういうコードはグローバルスコープの名前空間を汚染しているという事に気付いていますか?
気付いており理解した上で書いているという人、手を下ろして結構です。

if __name__ == '__main__'下はグローバルスコープ

そうです、if __name__ == '__main__'下はグローバルスコープです。
そこで定義された変数は、すべてグローバル変数になるということです。

先ほどのコードではローカル変数のつもりのargs, kind, data_list、がグローバル変数になってしまうのです。
ですので、以下のようなコードを書いてもエラーにはなりません。

def say_args(kind, data_list):
    print("kind:", kind, " data:", data_list)
    # 未定義のargsを参照してもエラーにならない、グローバル変数argsが参照される
    print("args: ", args)

※上記の一文は修正しています、詳しくは「2017-01-02 変数「x」について訂正」をご覧ください。

これは直接実行するとエラーにならず、モジュールとして呼ぶとName Errorになるという、やっかいなバグの元になります。

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

この件についてはPyCharm先生も「Shadows name 'kind' form outer scope」とお怒りです。

2017-01-01_11h04_25.png

解決方法

if __name__ == '__main__'下でコードを書きたい場合は、関数でくるみましょう。

#! usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function, absolute_import
import sys


def main(args):
    assert args

    kind = args[0].lower()
    data_list = [x.lower() for x in args[1:]]

    say_args(kind, data_list)


def say_args(kind, data_list):
    print("kind:", kind, " data:", data_list)


if __name__ == '__main__':
    main(sys.argv[1:])

main関数でくるむことによってグローバルスコープの名前空間は汚染されません。またmain関数でくるむことにより、main処理を冒頭に書けるのでコードの見通しがよくなります。

python初心者のみなさんは関数でくるんでおいた方が無難です。

他にもいろいろ心配という方はPyCharm先生を使ってください、それはもう小姑のように・・いえ、手取り足取りコードのダメな所を指摘してくれます。
pyflask先生 pyflakes先生やpylint先生でもかまいません、pylint先生の方が厳しめです。
(1/1 修正:pyflask先生はいません、pyflakes先生の間違いでした)

先生の言いたかったことは以上です。

それから「main関数でくるんだら、モジュールとして使いたい時に無駄なmain関数ができるじゃん」という人、後で職員室で先生と個別に話し合いましょう。

補講を書きました

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

2017-01-02 変数「x」について訂正

knoguchiさんのコメントを受けまして、訂正いたします。
記事中に訂正を入れると記事が読みにくくなりますので、文末に移動しています。

[訂正前の文章]
先ほどのコードではローカル変数のつもりのargs, kind, data_list一時変数のxまでもグローバル変数なってしまうのです。

def say_args(kind, data_list):
    print("kind:", kind, " data:", data_list)
    # 未定義のxを参照してもエラーにならない、グローバル変数xが参照される
    print("x: ", x)

内包表記内で使用されたxについてはグローバル変数にはなりません、エラーになります。

ただし、PyCharmの実行上はエラーにならないようです、これはPyCharmが高速化のためにグローバルフレームを再利用しているためです。
結果の確認はPyCharm上の実行だけではいけないという事ですね、教訓になりました。

改めてknoguchiさんにお礼を申し上げます。
コメント中の「リストコンプリヘンションの変数リークは再束縛の問題」は内包表記でハマりポイントの1つであるので、コメントも併せてご覧ください。