__main__ — 최상위 코드 환경

main — 최상위 코드 환경 (Top-level code environment)

Python에서 __main__이라는 특별한 이름은 두 가지 중요한 용도로 쓰여요. 하나는 프로그램의 최상위 환경의 이름으로, __name__ == '__main__' 표현으로 확인할 수 있어요. 또 하나는 Python 패키지 안의 __main__.py 파일이에요.

출처: Python 표준 라이브러리

본문

Python에서 __main__이라는 특별한 이름은 두 가지 중요한 구성을 가리켜요.

  • __name__ == '__main__' 표현으로 확인할 수 있는, 프로그램의 **최상위 환경(top-level environment)**의 이름
  • Python 패키지 안의 __main__.py 파일

둘 다 Python 모듈과, 사용자가 어떻게 상호작용하는지에 얽혀 있어요. 아래에서 자세히 설명할게요. 모듈이 처음이라면 튜토리얼의 Modules 섹션을 먼저 보시는 걸 추천해요.

__name__ == '__main__'

Python 모듈이나 패키지가 import되면 __name__이 그 모듈의 이름으로 설정돼요. 보통은 .py 확장자를 뺀 파일 이름이죠.

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

파일이 패키지의 일부라면 __name__에는 부모 패키지의 경로도 포함돼요.

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

그런데 모듈이 최상위 코드 환경에서 실행되면, __name__은 문자열 '__main__'으로 설정돼요.

"최상위 코드 환경"이 뭔가요?

__main__은 최상위 코드가 실행되는 환경의 이름이에요. 여기서 "최상위 코드(top-level code)"란 가장 먼저 실행되기 시작하는, 사용자가 지정한 첫 번째 Python 모듈을 말해요. 프로그램이 필요로 하는 다른 모든 모듈을 import하는 곳이라서 "최상위"라고 부르죠. 가끔은 애플리케이션의 **진입점(entry point)**이라고도 불러요.

최상위 코드 환경이 될 수 있는 경우를 볼게요.

  • 인터랙티브 프롬프트의 스코프:
>>> __name__
'__main__'
  • 인터프리터에 파일 인자로 넘겨진 Python 모듈:
$ python helloworld.py
Hello, world!
  • -m 인자와 함께 인터프리터에 넘겨진 Python 모듈이나 패키지:
$ python -m tarfile
usage: tarfile.py [-h] [-v] (...)
  • 표준 입력으로 인터프리터가 읽는 Python 코드:
$ echo "import this" | python
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
...
  • -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__':
    # Execute when the module is not initialized from an import statement.
    ...

더 자세한 내용 — 모든 상황에서 __name__이 어떻게 설정되는지 더 자세히 보려면 튜토리얼의 Modules 섹션을 확인하세요.

관용적 사용법 (Idiomatic Usage)

어떤 모듈은 커맨드라인 인자를 파싱하거나 표준 입력에서 데이터를 가져오는 등 스크립트로만 쓰려는 코드를 담고 있어요. 이런 모듈을 다른 모듈에서 import해서(예를 들어 단위 테스트하려고) 하면, 스크립트 코드가 의도치 않게 같이 실행돼요.

이때 if __name__ == '__main__' 코드 블록이 유용해져요. 이 블록 안의 코드는 모듈이 최상위 환경에서 실행될 때만 동작하거든요.

if __name__ == '__main__' 블록 아래에는 가능한 한 적은 문만 두는 게 코드의 명확성과 정확성을 높여요. 대부분 main이라는 함수가 프로그램의 주요 동작을 담당하게 하죠.

# echo.py

import shlex
import sys

def echo(phrase: str) -> None:
   """A dummy wrapper around print."""
   # for demonstration purposes, you can imagine that there is some
   # valuable and reusable logic inside this function
   print(phrase)

def main() -> int:
    """Echo the input arguments to standard output"""
    phrase = shlex.join(sys.argv)
    echo(phrase)
    return 0

if __name__ == '__main__':
    sys.exit(main())  # next section explains the use of sys.exit

만약 main 함수 안에 코드를 감싸지 않고 if __name__ == '__main__' 블록 안에 직접 넣었다면, phrase 변수가 모듈 전체의 전역 변수가 돼요. 이러면 모듈 안의 다른 함수들이 실수로 지역 이름 대신 그 전역 변수를 쓸 수도 있어서 오류가 나기 쉬워요. main 함수가 이 문제를 해결해 줘요.

main 함수를 쓰면 echo 함수 자체를 격리해서 다른 곳에서 import할 수 있다는 덕분도 있어요. echo.py를 import하면 echomain 함수가 정의되지만, __name__ != '__main__'이라 둘 다 호출되지는 않아요.

패키징 고려 사항 (Packaging Considerations)

main 함수는 커맨드라인 도구를 만들 때 자주 쓰여요. 콘솔 스크립트의 **엔트리 포인트(entry point)**로 지정해서 쓰거든요. 이렇게 하면 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 패키지가 처음이라면 튜토리얼의 Packages 섹션을 확인해 보세요. __main__.py 파일은 보통 패키지의 커맨드라인 인터페이스를 제공하는 데 쓰여요. 다음과 같은 가상의 "bandclass" 패키지를 생각해 볼게요.

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

__main__.py는 패키지가 -m 플래그로 커맨드라인에서 직접 호출될 때 실행돼요. 예를 들면:

$ 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는 **상대 import(relative import)**의 예시예요. 이 import 방식은 패키지 안의 모듈을 참조할 때 쓸 수 있어요. 자세한 내용은 튜토리얼의 Intra-package References를 보세요.

관용적 사용법

__main__.py의 내용은 보통 if __name__ == '__main__' 블록으로 감싸지 않아요. 대신 그 파일은 짧게 유지하고, 다른 모듈에서 실행할 함수를 import해요. 그러면 그 다른 모듈들을 쉽게 단위 테스트하고 제대로 재사용할 수 있거든요.

패키지 안의 __main__.py 파일에 if __name__ == '__main__' 블록을 쓰면 여전히 예상대로 동작해요. 그러나 import되면 그 __name__ 속성에 패키지 경로가 포함되죠.

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

하지만 .zip 파일의 루트 디렉토리에 있는 __main__.py에는 이 방식이 동작하지 않아요. 그래서 일관성을 위해 __name__ 검사가 없는 최소한의 __main__.py를 선호해요.

더 알아보기

  • 표준 라이브러리에서 if __name__ == '__main__' 블록이 없는 최소한의 __main__.py를 가진 패키지의 예시로 venv 를 보세요. python -m venv [directory]로 실행할 수 있어요.
  • 인터프리터 실행 파일의 -m 플래그에 대한 자세한 내용은 runpy 를 보세요.
  • .zip 파일로 패키징된 애플리케이션을 어떻게 실행하는지는 zipapp 을 보세요. 이 경우 Python은 아카이브 루트 디렉토리에서 __main__.py 파일을 찾아요.

import __main__

Python 프로그램이 어떤 모듈로 시작됐든, 같은 프로그램 안에서 실행되는 다른 모듈들은 __main__ 모듈을 import해서 최상위 환경의 스코프(네임스페이스)에 접근할 수 있어요. 이것은 __main__.py 파일을 import하는 게 아니라, 특별한 이름 '__main__'을 받은 모듈을 import하는 거예요.

다음은 __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__을 import한다고 해서 start 모듈의 if __name__ == "__main__" 블록에 넣어 둔 스크립트용 최상위 코드가 의도치 않게 실행되는 문제는 생기지 않아요. 왜 그럴까요?

Python은 인터프리터 시작 시 sys.modules에 빈 __main__ 모듈을 넣고, 최상위 코드를 실행하면서 그걸 채워요. 우리 예시에서는 이게 start 모듈이고, 이 모듈은 한 줄씩 실행되면서 namely를 import해요. 그리고 namely__main__(사실상 start)을 import하죠. 이건 **import 순환(cycle)**이에요! 다행히 부분적으로 채워진 __main__ 모듈이 sys.modules에 있으니, Python은 그것을 namely에 넘겨줘요. 이 작동 방식에 대한 자세한 내용은 import 시스템 참조의 Special considerations for 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__ 스코프는 pdbrlcompleter의 구현에도 사용돼요.