"__main__" --- トップレベルのコード環境
***************************************

======================================================================

Python では、 "__main__" という特別な名前が次の二つの重要な用途で使わ
れます:

1. プログラムのトップレベル環境の名前。 "__name__ == '__main__'" とい
   う式でチェックすることができる。

2. Python パッケージにおける "__main__.py" ファイル。

どちらも Python のモジュールに関わる機能です。 １つ目はユーザーがどう
モジュールを使うか、２つ目はモジュールとモジュールがどうやりとりするか
に関係します。 詳細は以下で説明します。 Python モジュールがどういうも
のかについては、 モジュール を参照してください。


"__name__ == '__main__'"
========================

Python モジュールやパッケージがインポートされるとき、 "__name__" の値
はそのモジュールの名前となります。 通常、インポートされる Python ファ
イル自体のファイル名から拡張子 ".py" を除いたものとなります:

   >>> import configparser
   >>> configparser.__name__
   'configparser'

インポートされるファイルがパッケージの一部である場合は、 "__name__" に
はそのパッケージのパスも含まれます:

   >>> from concurrent.futures import process
   >>> process.__name__
   'concurrent.futures.process'

しかし、モジュールがトップレベルのスクリプト環境で実行される場合は、
"__name__" が "'__main__'" という文字列になります。


「トップレベルのスクリプト環境」とは
------------------------------------

"__main__" は、トップレベルのコードが実行される環境の名前です。 "トッ
プレベルのコード" は、実行を開始する最初のユーザー指定の Python モジュ
ールです。これは、このモジュールがプログラムに必要なすべての他のモジュ
ールをインポートするために、 "トップレベル" なのです。時折、 "トップレ
ベルのコード" は、アプリケーションには *エントリーポイント* と呼ばれま
す。

以下のものがトップレベルのスクリプト環境となります:

* インタラクティブプロンプトのスコープ:

     >>> __name__
     '__main__'

* Python インタープリタにファイル引数として渡される Python モジュール:

     $ python helloworld.py
     Hello, world!

* Python インタープリタにPython "-m" オプションとして渡される Python
  モジュールまたはパッケージ:

     $ python -m tarfile
     usage: tarfile.py [-h] [-v] (...)

* 標準入力から Python インタープリタが読み込む Python コード:

     $ echo "import this" | python
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

* Python インタープリタに "-c" オプションで渡される Python コード:

     $ python -c "import this"
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

上記それぞれの場合で、トップレベルのモジュールの "__name__" の値が
"'__main__'" となります。

これにより、 "__name__" をチェックすれば各モジュールは自分がトップレベ
ル環境で実行されているかどうかを知ることができます。 このことから、モ
ジュールが import 文で初期化された場合以外の場合でのみコードを実行する
ため、次のコードがしばしば用いられます:

   if __name__ == '__main__':
       # モジュールが import 文で初期化された場合以外に実行する。
       ...

参考:

  あらゆる場合に "__name__" の値がどうセットされるのかについて、詳しく
  はチュートリアルの モジュール セクションを参照してください。


通常の使われ方
--------------

一部のモジュールでは、コマンドライン引数をパースしたり標準入力からデー
タを取得したなど、スクリプト用途のみのコードが含まれています。 このよ
うなモジュールが、例えばユニットテストのため、別のモジュールからインポ
ートされると、そのスクリプト用コードが意図に反して実行されてしまいます
。

"if __name__ == '__main__'" というコードは、このようなときに役立ちます
。 このブロックの中にあるコードは、当該のモジュールがトップレベル環境
で実行されていない限り、実行されません。

"if __name__ == '__main__'" の下のブロックにあるコードはできるだけ少な
くした方が、コードの分かりやすさや正確さにつながります。 最もよくある
のが、プログラムの主要な処理を "main" 関数の中にカプセル化することです
:

   # echo.py

   import shlex
   import sys

   def echo(phrase: str) -> None:
      """print を呼び出すだけのダミーのラッパー。"""
      # 説明のため、この関数に有用で再利用可能な処理が
      # 含まれていると考える
      print(phrase)

   def main() -> int:
       """入力された引数を標準出力に出力する"""
       phrase = shlex.join(sys.argv)
       echo(phrase)
       return 0

   if __name__ == '__main__':
       sys.exit(main())  # sys.exit の使い方は次の節で説明

注意すべき点として、もし "main" 関数内のコードをカプセル化せず "if
__name__ == '__main__'" の下に直接書いた場合、 "phrase" 変数はモジュー
ル全体からグローバルにアクセスできてしまいます。 モジュール内の他の関
数が意図せずローカル変数ではなくそのグローバル変数を使用してしまう可能
性があるため、ミスにつながります。 "main" 関数を用意することでこの問題
は解決できます。

"main" 関数を使うことのもう一つのメリットとして、 "echo" 関数が分離し
、別の場所からインポートできるようになることです。 "echo.py" がインポ
ートされるとき、 "echo" 関数と "main" 関数が定義されますが、 "__name__
!= '__main__'" であるため、どちらの関数も呼び出されません。


パッケージングで考慮すべき点
----------------------------

"main" 関数は、コンソールスクリプトのエントリーポイントに指定して、コ
マンドラインツールを作るためによく使われます。 この場合、pip は関数呼
び出しをテンプレートのスクリプトに挿入し、"main" の戻り値を
"sys.exit()" に渡します。 例えば、次のようになります。

   sys.exit(main())

"main" の呼び出しは "sys.exit()" で囲まれるため、関数は "sys.exit()"
の入力として受け付けられる値を返すことが想定されています。 通常は整数
か "None" です (関数に return 文がない場合は暗黙に "None" を返します)
。

最初からこの慣習に従っておくと、モジュールを直接実行した場合 ("python
echo.py" など) と、後で pip でインストール可能なパッケージのコンソール
スクリプトのエントリーポイントとして配布した場合とで、同じ動作になりま
す。

特に、"main" 関数から文字列を返す場合は注意してください。 "sys.exit()"
は文字列の引数を失敗のメッセージとして扱うため、プログラムは失敗を示す
終了コード "1" で終了し、その文字列は "sys.stderr" に出力されます。 先
ほどの "echo.py" は、"sys.exit(main())" の慣習に従った例です。

参考:

  Python Packaging User Guide には、現在使われているツールで Python パ
  ッケージを配布・インストールする方法について、チュートリアルとリファ
  レンスがまとめられています。


Python パッケージの "__main__.py"
=================================

Python パッケージについてよく知らない場合は、チュートリアルの パッケー
ジ を参照してください。 "__main__.py" ファイルは、主にパッケージのコマ
ンドラインインターフェースを提供するために使われます。 次のような「
bandclass」という架空のパッケージを考えてみます。

   bandclass
     ├── __init__.py
     ├── __main__.py
     └── student.py

コマンドラインから "-m" フラグでパッケージ自体を直接実行すると、
"__main__.py" が実行されます。 例を示します。

   $ python -m bandclass

このコマンドで "__main__.py" が実行されます。 この仕組みの使い方は、作
成するパッケージの性質によります。 この架空の例では、教師が生徒を検索
できるようにするとよいでしょう。

   # bandclass/__main__.py

   import sys
   from .student import search_students

   student_name = sys.argv[1] if len(sys.argv) >= 2 else ''
   print(f'Found student: {search_students(student_name)}')

"from .student import search_students" は相対インポートの例です。 この
書き方は、パッケージ内のモジュールを参照するときに使えます。 詳しくは
、チュートリアルの モジュール にある パッケージ内参照 を参照してくださ
い。


通常の使われ方
--------------

通常、"__main__.py" の内容は "if __name__ == '__main__'" ブロックでは
囲みません。 代わりに、ファイルを短く保ち、実行する関数を他のモジュー
ルからインポートします。 そうすれば、それらのモジュールを簡単にユニッ
トテストでき、再利用もしやすくなります。

パッケージ内の "__main__.py" で "if __name__ == '__main__'" ブロックを
使った場合も、期待どおりに動作します。 インポートされたときには、
"__name__" 属性にパッケージのパスが含まれるためです。

   >>> import asyncio.__main__
   >>> asyncio.__main__.__name__
   'asyncio.__main__'

ただし、".zip" ファイルのルートディレクトリにある "__main__.py" では、
この方法は使えません。 そのため、一貫性を保つには、"__name__" のチェッ
クを含まない最小限の "__main__.py" にする方がよいでしょう。

参考:

  標準ライブラリ内で最小限の "__main__.py" を持つパッケージの例は、
  "venv" を参照してください。 "if __name__ == '__main__'" ブロックは含
  まれていません。 "python -m venv [directory]" で実行できます。

  インタープリタ実行ファイルの "-m" フラグの詳細は、"runpy" を参照して
  ください。

  *.zip* ファイルにまとめたアプリケーションの実行方法は、"zipapp" を参
  照してください。 この場合、Python はアーカイブのルートディレクトリに
  ある "__main__.py" を探します。


"import __main__"
=================

Python プログラムをどのモジュールから起動したかにかかわらず、同じプロ
グラム内の他のモジュールは、"__main__" モジュールをインポートすること
でトップレベル環境のスコープ (*名前空間*) をインポートできます。 これ
は "__main__.py" というファイルのインポートではなく、特別な名前
"'__main__'" が割り当てられたモジュールのインポートです。

"__main__" の名前空間を利用するモジュールの例を示します。

   # namely.py

   import __main__

   def did_user_define_their_name():
       return 'my_name' in dir(__main__)

   def print_user_name():
       if not did_user_define_their_name():
           raise ValueError('Define the variable `my_name`!')

       print(__main__.my_name)

このモジュールは、例えば次のように使えます。

   # start.py

   import sys

   from namely import print_user_name

   # my_name = "Dinsdale"

   def main():
       try:
           print_user_name()
       except ValueError as ve:
           return str(ve)

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

このプログラムを起動すると、次のような結果になります。

   $ python start.py
   Define the variable `my_name`!

プログラムの終了コードは、エラーを示す 1 になります。 "my_name =
"Dinsdale"" の行のコメントを外すとプログラムは正しく動作し、成功を示す
終了コード 0 で終了します。

   $ python start.py
   Dinsdale

"__main__" をインポートしても、"start" モジュールの "if __name__ ==
"__main__"" ブロックにある、スクリプト用のトップレベルコードが意図せず
実行されることはありません。 なぜこのように動作するのでしょうか。

Python はインタープリタの起動時に空の "__main__" モジュールを
"sys.modules" に追加し、トップレベルコードを実行してその内容を埋めてい
きます。 この例では、"start" モジュールが一行ずつ実行され、"namely" を
インポートします。 今度は "namely" が "__main__" (実体は "start") をイ
ンポートするので、循環インポートになります。 しかし、内容が部分的に埋
まった "__main__" モジュールが "sys.modules" にあるため、Python はそれ
を "namely" に渡せます。 詳しい仕組みは、インポートシステムのリファレ
ンスの __main__ に関する特別な考慮事項 を参照してください。

Python の REPL も「トップレベル環境」の一例なので、REPL で定義したもの
はすべて "__main__" のスコープの一部になります。

   >>> import namely
   >>> namely.did_user_define_their_name()
   False
   >>> namely.print_user_name()
   Traceback (most recent call last):
   ...
   ValueError: Define the variable `my_name`!
   >>> my_name = 'Jabberwocky'
   >>> namely.did_user_define_their_name()
   True
   >>> namely.print_user_name()
   Jabberwocky

"__main__" のスコープは、"pdb" と "rlcompleter" の実装で使われています
。
