数弱の文系大学生によるブログです。

Python、Webデザイン関連の記事を投稿していけたらなと思っています。

備忘録的なブログなので、読みにくい文章だと思います。ご了承ください。

Python3の復習(アンダーバー・アンダースコアの意味)

こんにちは。

万年金欠のかにたまごです。


今回は

  • アンダーバー・アンダースコアの意味

について、復習していこうと思います。

この単元は「頭の隅に置いておこうかなレベルの話だ」と思って前回の記事と同じ括りで書いていたのですが、思ったより長くなってしまったので別の記事にしました。


今までのPython3 復習履歴は以下の通り。

  1. 数値・文字列
  2. リスト・タプル
  3. 辞書・集合
  4. if文・while文・for文
  5. 内包表記
  6. 関数
  7. ジェネレータ
  8. デコレータ
  9. 名前空間とスコープ

アンダーバー・アンダースコアの意味

globals()の出力でアンダーバーから始まりアンダーバーで終わる変数がある(以降アンダーバーで統一します)。

print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x009D62B0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\***\\Desktop\\Python\\Test\\blog_test.py', '__cached__': None}

このアンダーバーはPythonで使う変数として予約されていることを示すらしい。デフォルトで定義されているグローバル変数って認識で大丈夫かな・・・。

せっかくなので中身を確認してみる。

for key, value in globals().items():
    print("KEY: ", key)
    print("VALUE: ", value)
    print()
KEY:  __name__
VALUE:  __main__

Traceback (most recent call last):
  File "C:\Users\***\Desktop\Python\Test\blog_test.py", line 611, in <module>
    for key, value in globals().items():
RuntimeError: dictionary changed size during iteration


おいおい、ここで詰まるかおいおい(;´д`)


エラー文を直訳すると

反復中に辞書のサイズが変更された

となる。


どういうことだろうと思っていたら、こちらに解決方法が載ってました。
globals()をリスト化すれば通るみたいです。

for key, value in list(globals().items()):
    print("KEY: ", key)
    print("VALUE: ", value)
    print()

出力結果

KEY:  __name__
VALUE:  __main__

KEY:  __doc__
VALUE:  None

KEY:  __package__
VALUE:  None

KEY:  __loader__
VALUE:  <_frozen_importlib_external.SourceFileLoader object at 0x035162B0>

KEY:  __spec__
VALUE:  None

KEY:  __annotations__
VALUE:  {}

KEY:  __builtins__
VALUE:  <module 'builtins' (built-in)>

KEY:  __file__
VALUE:  C:\Users\***\Desktop\Python\Test\new_test.py

KEY:  __cached__
VALUE:  None


はえ~と思ってみていたら、__name__と__main__が目につきました。
これはよく目にするやつだ・・・。目にはしてたけど、意味は特に考えずに写経してたやつだ・・・。

こんな感じのやつ。

def main():
    print("ここのmainメソッドでまとめて実行")

if __name__ == "__main__":
    main()


少し調べてみると、以下のようなことが分かりました。

  • __name__には、そのファイルが実行された際に割り当てられる名前が格納されている
  • ファイルを直接実行した場合、__name__には__main__という文字列が割り当てられる
  • 他ファイルから間接的に実行された場合は、ファイル名が__name__に格納される

まず一つ目と二つ目の

__name__には、そのファイルが実行された際に割り当てられる名前が格納されている
ファイルを直接実行した場合、__name__には__main__という文字列が割り当てられる

の確認。

#new_test.pyというファイル
print(__name__)

これをコマンドラインもしくはIDEで実行してみると

__main__

ファイルを直接実行したので、上記の通り__main__と表示される。


そして、三つ目の

他ファイルから間接的に実行された場合は、ファイル名が__name__に格納される

の確認。


の前に、間接的な実行とはなんなのか。

それは他のファイルから読み込まれたとき、つまりインポートされたとき

まずはソレの確認から。

#new_test.pyというファイル
def test():
    print("new_test.py が実行されました。")
test()

上のファイルを他のファイルでインポートしてみる。

#blog_test.pyというファイル
import new_test

そして、このblog_test.pyを実行してみると

new_test.py が実行されました。

というように、インポートされた時点でnew_test.pyが実行されている。

これが間接的な実行。


続きの

他ファイルから間接的に実行された場合は、ファイル名が__name__に格納される

という点の確認。

import new_test
print(new_test.__name__)

インポートしてきたnew_test.pyの__name__を確認すると

new_test.py が実行されました。
new_test

__name__には、インポートしてきたnew_test.pyのファイル名(new_test)が格納されていることが確認できる。



これらを確認した上で改めて以下のコードを考えてみると

def main():
    print("ここのmainメソッドでまとめて実行")

if __name__ == "__main__":
    main()


ファイルが間接的でなく、コマンドラインIDEで直接的に実行されたときにだけmain()を呼ぶ

というように解釈できる。


けど、ここでこんなことを思う人が多いんじゃないかな、と思う。


なぜ直接実行されたときにだけmain()を呼び出す必要があるのか。


その理由は、例えば以下のようなコードがあった場合を考えてみると少し理解できた気がした。

def olympic_calc():
    """
    現在の時刻を取得してオリンピックの開催日との差分を計算。
    なんやかんやして"あと何日何時何分何秒"の形式に変換。
    そして結果を返す。
    """
    return date

def send_mail(something):
    """
    受け取ったsomethingをなんやかんやして自分のメールアドレスに送り付ける。
    """

def main():
    #オリンピックまであと何日か、を計算して返すメソッド
    date = olympic_calc()
    #それを自分のメールアドレスに送り付けるメソッド
    send_mail(date)

main()

このファイル自体を実行する分には問題はないと思われる。

けども

「このファイルのolympic_calcメソッドを使ってWebページに掲載したい!」
「このファイルのsend_mailメソッドを使って新着アニメ情報を受け取りたい!」

と思った場合どうでしょう。

olympic_calcメソッドとsend_mailメソッドが定義されているファイル内で使用する分にはよいですが、他のファイルから使用する場合はどうでしょう。

きっと


インポートだっ(゚Д゚)


と思うはずです。

そして、いざインポートしてみると


Webページに掲載したいだけなのに自分のメールアドレスにも送られてくる・・・
新着アニメ情報が欲しいだけなのに、なんかの日にちも送られてくる・・・


という状態に。


ここでこう思うはずです。


インポートされた時点でmain()が実行されてしまうからや( ^ω^)・・・


というわけで、


ファイルが直接的に実行される場合以外はmainメソッドは呼び出さず、その他諸々の関数や変数を参照できるようにするため

def main():
    print("ここのmainメソッドでまとめて実行")

if __name__ == "__main__":
    main()

と記述するのだと解釈しました。

今までただ何とな~く書いていた部分が少し理解できたので良かったです。


以上です。


参考にさせていただいたサイト:

Pythonのif __name__ == "__main__" とは何ですか?への回答