Edited at

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

More than 1 year has passed since last update.

あけましておめでとうございます。

新年早々ですが、先生、みなさんに言いたいことがあります。

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つであるので、コメントも併せてご覧ください。