LoginSignup
265
192

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-01

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

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

265
192
10

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
265
192