"__main__" --- 최상위 코드 환경
*******************************

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

파이썬에서 특수 이름 "__main__" 은 두 가지 중요한 구조에 사용됩니다:

1. 프로그램의 최상위 환경 이름으로, "__name__ == '__main__'" 식을 사용
   하여 확인 할 수 있습니다; 그리고

2. 파이썬 패키지의 "__main__.py" 파일입니다.

이 두 메커니즘은 파이썬 모듈과 관련이 있으며, 사용자가 그것과 상호작용
을 하는 방식과 그들끼리 상호작용을 하는 방식에 관한 것입니다. 자세한
내용은 아래에서 설명합니다. 파이썬의 모듈이 처음이라면 자습서의 모듈
를 참고하십시오.


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

파이썬 모듈이나 패키지가 임포트 되면, "__name__" 은 해당 모듈의 이름으
로 설정됩니다. 보통, 이름은 ".py" 확장자를 제외한 파이썬 파일 자체의
이름입니다:

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

파일이 패키지의 일부라면, "__name__" 은 상위 패키지 경로도 포함합니다:

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

그러나 모듈이 최상위 코드 환경에서 실행될 경우, "__name__" 은 문자열
"__main__" 으로 설정됩니다.


"최상위 코드 환경" 이란 무엇인가요?
-----------------------------------

"__main__" 은 최상위 코드가 실행되는 환경의 이름입니다. "최상위 코드"
란 사용자가 지정한 첫 번째 파이썬 모듈로, 실행을 시작하는 모듈입니다.
"최상위" 라고 부르는 이유는 프로그램이 필요로 하는 다른 모든 모듈을 임
포트하기 때문입니다. 때때로 "최상위 코드" 는 응용 프로그램의 *진입 지
점* 이라고도 합니다.

최상위 코드 환경은 다음과 같습니다:

* 대화형 프롬프트의 스코프:

     >>> __name__
     '__main__'

* 파이썬 인터프리터에 파일 인자로 전달된 파이썬 모듈:

     $ python helloworld.py
     Hello, world!

* 파이썬 인터프리터 "-m" 인자로 전달된 파이썬 모듈이나 패키지:

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

* 표준 입력으로부터 파이썬 인터프리터가 읽은 코드:

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

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

* 파이썬 인터프리터에 "-c" 인자로 전달된 파이썬 코드:

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

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

이러한 모든 경우에서, 최상위 모듈의 "__name__" 은 "'__main__'" 으로 설
정됩니다.

결과적으로, 모듈은 자신의 "__name__" 을 검사하여 최상위 환경에서 실행
중인지를 확인할 수 있습니다. 이 때문에 모듈이 임포트 문에서 초기화되지
않을 때 조건부로 코드를 실행하는 공통 관용구를 사용할 수 있습니다:

   if __name__ == '__main__':
       # 모듈이 임포트 문에서 초기화되지 않을 때 실행합니다.
       ...

더 보기: "__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" 입니다.

이 규칙을 따름으로써, 모듈은 나중에 pip로 설치 가능한 패키지의 콘솔 스
크립트 진입 지점으로 배포되더라도, 직접 실행할 때 (예를 들어 "python
echo.py")와 동일한 동작을 하게 됩니다.

특히 "main" 에서 문자열을 반환할 때 주의해야 합니다. "sys.exit()" 함수
는 문자열 인자를 실패 메시지로 해석하므로, 프로그램의 종료 코드는 실패
를 의미하는 "1" 이 되고, 문자열은 "sys.stderr" 에 기록됩니다. 앞서 언
급한 "echo.py" 의 예제는 "sys.exit(main())" 규칙 사용을 잘 보여줍니다.

더 보기:

  Python Packaging User Guide 는 현대적인 도구를 사용하여 파이썬 패키
  지를 배포하고 설치하는 방법에 대한 자습서와 참고 자료를 제공합니다.


파이썬 패키지의 "__main__.py"
=============================

파이썬 패키지에 익숙하지 않다면, 자습서의 패키지 를 참고하십시오. 대부
분의 경우 "__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" 모듈
  을 참고하십시오. 이 경우 파이썬은 아카이브의 루트 디렉터리에서
  "__main__.py" 파일을 찾습니다.


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

파이썬 프로그램이 어떤 모듈에서 시작되었는지와 관계없이, 같은 프로그램
내의 다른 모듈들은 "__main__" 모듈을 임포트하여 최상위 환경의 스코프
(*namespace*)를 불러올 수 있습니다. 이는 "__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`!')

       if '__file__' in dir(__main__):
           print(__main__.my_name, "found in file", __main__.__file__)
       else:
           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 found in file /path/to/start.py

"__main__" 을 임포트하더라도, "start" 모듈의 "if __name__ ==
'__main__'" 블록에 있는 스크립트 전용 코드가 의도치 않게 실행되는 문제
는 발생하지 않습니다. 왜 그럴까요?

파이썬은 인터프리터 시작 시 "sys.modules" 에 빈 "__main__" 모듈을 삽입
하고, 최상위 코드를 실행하면서 이를 채웁니다. 이 예제에서 그 최상위 모
듈은 "start" 이며, 한 줄씩 실행되면서 "namely" 를 임포트합니다. 그런데
"namely" 는 다시 "__main__" (즉, 실제로는 "start") 을 임포트합니다. 이
는 임포트 순환입니다! 다행히도, 부분적으로 채워진 "__main__" 모듈이 이
미 "sys.modules" 에 존재하므로, 파이썬은 그것을 "namely" 에 전달합니다
. 이 동작 방식에 대한 자세한 내용은 import 시스템 레퍼런스의 __main__
에 대한 특수 고려 사항 을 참고하십시오.

파이썬 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

이 경우 REPL은 대화형 환경이므로, "__main__" 스코프에는 "__file__" 속
성이 존재하지 않습니다.

"__main__" 스코프는 "pdb" 와 "rlcompleter" 의 구현에서도 사용됩니다.
