[Final] PEP 397 - Python launcher for Windows

원문 링크: PEP 397 - Python launcher for Windows

상태: Final 유형: Standards Track 작성일: 15-Mar-2011

PEP 397 – Windows용 Python 런처

  • 작성자: Mark Hammond, Martin von Löwis
  • 상태: Final (최종)
  • 유형: Standards Track
  • 생성일: 2011년 3월 15일
  • Python 버전: 3.3

요약 (Abstract)

이 PEP는 Windows 플랫폼용 Python 런처를 설명합니다. Python 런처는 지정된 명령줄 인수를 사용하여 Python 실행 파일을 찾아 실행하기 위해 여러 발견적 방법(heuristics)을 사용하는 단일 실행 파일입니다.

도입 배경 (Rationale)

Windows는 “파일 연결(file associations)”을 제공하여 실행 파일을 확장자와 연결할 수 있게 함으로써 특정 상황(예: Windows Explorer에서 파일을 두 번 클릭)에서 스크립트를 직접 실행할 수 있도록 합니다. 이전까지는 “가장 마지막에 설치된 Python이 승리”하는 전략이 사용되었으며, Python 2.x 릴리스의 보수적인 변경 사항 덕분에 이상적이지는 않지만 대체로 작동 가능했습니다.

그러나 Python 3.x 스크립트는 종종 Python 2.x 스크립트와 구문적으로 호환되지 않으므로, 스크립트가 대상으로 하는 Python 버전에 따라 ‘.py’ 확장자를 가진 파일이 다른 실행 파일을 사용하도록 허용하는 다른 전략이 필요합니다. 이는 다른 운영 체제의 기존 방식을 차용하여 이루어질 것입니다. 스크립트는 아래에 설명된 “shebang” 라인을 통해 필요한 Python 버전을 지정할 수 있게 됩니다.

Unix 계열 운영 체제(이 PEP에서는 간단히 “Unix”라고 칭함)는 스크립트 실행에 사용될 실제 실행 파일을 지정하는 “shebang” 라인을 검사하여 스크립트가 실행 파일처럼 실행될 수 있도록 합니다. 이는 execve(2) man 페이지에 자세히 설명되어 있습니다.

또한, 이 운영 체제들은 잘 알려진 디렉터리에 Python 실행 파일에 대한 심볼릭 링크(symbolic-links)를 제공합니다. 예를 들어, 많은 시스템에는 운영 체제에 설치된 특정 버전의 Python을 참조하는 /usr/bin/python 링크가 있습니다. 이 심볼릭 링크는 Python이 실제로 머신에 설치된 위치에 관계없이 (즉, shebang 라인이나 PATH에 Python이 실제로 설치된 경로를 참조할 필요 없이) Python을 실행할 수 있도록 합니다. PEP 394 ‘Unix-Like Systems의 “python” 명령’은 특정 Python 버전을 보다 세분화하여 지정하기 위한 추가적인 규칙을 설명합니다.

이 두 가지 기능을 결합하면 Python을 대화식으로 시작하고 Python 스크립트를 실행할 수 있는 이식성 있고 어느 정도 예측 가능한 방법을 제공합니다. 이 PEP는 Windows 플랫폼에서 Python에 동일한 이점을 제공할 수 있는 런처 구현을 설명하며, 따라서 런처가 여러 Python 버전을 동시에 지원하기 위해 ‘.py’ 파일과 연결된 실행 파일이 될 수 있도록 합니다.

이 PEP는 Windows와 Unix 모두에서 작동해야 하는 shebang 라인을 사용할 수 있는 기능을 제공하지만, 이것이 이 PEP의 주요 동기는 아닙니다. 주요 동기는 새로운 구문이나 규칙을 만들지 않고 특정 버전을 지정할 수 있도록 하는 것입니다.

사양 (Specification)

이 PEP는 런처의 기능을 명시합니다. 프로토타입 구현은에 제공되며 Python의 Windows 설치 프로그램과 함께 배포되지만, 별도로도 (Python 설치 프로그램과 함께 릴리스됨) 사용할 수 있습니다. 여기에 명시된 기능이 계속 작동하는 한 런처에 새로운 기능이 추가될 수 있습니다.

설치 (Installation)

런처는 두 가지 버전으로 제공됩니다. 하나는 콘솔 프로그램이고 다른 하나는 “windows” (즉, GUI) 프로그램입니다. 이 두 런처는 현재 Python과 함께 제공되는 ‘python.exe’ 및 ‘pythonw.exe’ 실행 파일에 해당합니다. 콘솔 런처는 ‘py.exe’로, Windows 런처는 ‘pyw.exe’로 명명됩니다. “windows” (즉, GUI) 버전의 런처는 가상 shebang 라인이 단순히 “python”을 지정하더라도 pythonw.exe를 찾아서 실행하려고 시도합니다. 사실, 가상 shebang 라인에서는 w 접미사 표기법이 전혀 지원되지 않습니다.

런처는 권한 있는 사용자(privileged user)에 의해 설치되면 Windows 디렉터리에 설치됩니다. 독립형 설치 프로그램은 설치 프로그램의 대체 위치를 묻고, 해당 위치를 사용자의 PATH에 추가합니다.

Windows 디렉터리에 설치되는 런처는 32비트 실행 파일입니다. 독립형 설치 프로그램은 런처의 64비트 버전을 설치하는 것도 제공할 수 있습니다.

런처 설치는 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CurrentVersion\SharedDLLs에 참조 카운터와 함께 등록됩니다. 이는 배포되는 pythonXY.dll의 버전 번호와 일치하는 버전 리소스를 포함합니다. 독립적인 설치는 이전 버전의 런처를 새 버전으로 덮어씁니다. 독립형 릴리스는 기반이 되는 CPython 릴리스의 FIELD3에서 0x10의 릴리스 레벨을 사용합니다.

일단 설치되면, 런처의 “콘솔” 버전은 .py 파일과 연결되고 “windows” 버전은 .pyw 파일과 연결됩니다.

런처는 특정 Python 버전에 묶여 있지 않습니다. 예를 들어, Python 3.3과 함께 배포된 런처는 모든 Python 2.x 및 Python 3.x 버전을 찾아서 실행할 수 있어야 합니다. 그러나 런처 바이너리는 릴리스되는 Python 바이너리의 버전 리소스와 동일한 버전 리소스를 가집니다.

Python 스크립트 실행 (Python Script Launching)

런처는 Python 스크립트 실행에만 제한됩니다. 일반적인 스크립트 런처 또는 shebang 프로세서로 의도된 것이 아닙니다.

런처는에 설명된 shebang 라인 구문을 지원하며, 나열된 모든 제한 사항도 포함합니다.

런처는 다음 (정규식) 접두사 중 하나를 가진 Python 실행 파일을 참조하는 shebang 라인을 지원합니다: “/usr/bin/”, “/usr/local/bin” 및 “/usr/bin/env *”, 그리고 접두사 없이 지정된 바이너리도 지원합니다.

예를 들어, shebang 라인이 '#! /usr/bin/python'이면 Windows의 상대 디렉터리 "\usr\bin"에 실행 파일이 없을 가능성이 높더라도 작동해야 합니다. 이는 많은 스크립트가 단일 shebang 라인을 사용하여 수정 없이 Unix와 Windows 모두에서 작동할 수 있음을 의미합니다.

런처는 실행 파일에 대한 완전한 경로(fully-qualified paths)를 지원합니다. 이로 인해 스크립트의 이식성이 떨어지지만, Unix에서 제공하는 기능이며 경우에 따라 Windows 사용자에게 유용할 것입니다.

런처는 CPython 이외의 구현(예: Jython 및 IronPython)을 지원할 수 있지만, Unix에 공통 링크(예: “/usr/bin/jython”)가 없다는 점과 런처가 Windows에서 이러한 구현의 설치 위치를 자동으로 찾을 수 없다는 점을 고려할 때, 런처는 사용자 지정 옵션을 통해 이를 지원할 것입니다. 이를 활용하는 스크립트는 이식성이 떨어지지만 (런처가 실행되는 머신의 구성을 반영하도록 사용자 지정 옵션을 설정해야 함) 이 기능은 그럼에도 불구하고 가치 있다고 간주됩니다.

Unix에서는 사용자가 /usr/bin의 링크를 원하는 버전으로 조정하여 사용할 특정 Python 버전을 제어할 수 있습니다. Windows의 런처는 Windows 링크를 사용하지 않으므로, 사용자 지정 옵션(환경 변수 및 INI 파일 모두를 통해 노출됨)이 어떤 버전의 Python이 사용될지 결정하는 의미를 재정의하는 데 사용됩니다. 예를 들어, shebang 라인이 "/usr/bin/python2"이면 Python 2.x 구현을 자동으로 찾지만, 환경 변수는 정확히 어떤 Python 2.x 구현이 선택될지 재정의할 수 있습니다. "/usr/bin/python""/usr/bin/python3"도 마찬가지입니다. 이는 이 PEP의 뒷부분에서 자세히 명시됩니다.

Shebang 라인 파싱 (Shebang line parsing)

첫 번째 명령줄 인수가 대시(‘-‘) 문자로 시작하지 않으면, 해당 인수를 파일로 열어의 규칙에 따라 shebang 라인을 파싱하려고 시도합니다.

#! interpreter [optional-arg]

파싱되면 명령은 다음 규칙에 따라 분류됩니다.

  • 명령이 사용자 지정 명령의 정의로 시작하고 그 뒤에 공백 문자(줄 바꿈 포함)가 오면, 사용자 지정 명령이 사용됩니다. 사용자 지정 명령에 대한 설명은 아래를 참조하십시오.
  • 런처는 Python을 실행하기 위한 Unix 호환 명령으로 간주되는 일련의 접두사를 정의합니다. 즉, "/usr/bin/python", "/usr/local/bin/python", "/usr/bin/env python", 및 "python"입니다. 명령이 이러한 문자열 중 하나로 시작하면 ‘가상 명령(virtual command)’으로 처리되며, 사용할 실행 파일을 찾기 위해 Python 버전 한정자(아래 참조)에 설명된 규칙이 사용됩니다.
  • 그렇지 않으면 명령은 직접 실행할 준비가 된 것으로 가정됩니다. 즉, 완전한 경로(또는 PATH의 실행 파일 참조) 뒤에 선택적으로 인수가 올 수 있습니다. 문자열의 내용은 파싱되지 않으며, 스크립트 이름과 런처 명령줄 인수가 추가된 후 Windows CreateProcess 함수에 직접 전달됩니다. 이는 CreateProcess에서 사용되는 규칙(상대 경로 이름 및 확장자 없는 실행 파일 참조 처리 방식 포함)이 사용됨을 의미합니다. 특히, Windows 명령 처리기는 사용되지 않으므로 명령 처리기에서 사용되는 특별한 규칙(예: ‘.exe’ 이외의 확장자 자동 추가, 배치 파일 지원 등)은 사용되지 않습니다.

‘가상(virtual)’ shebang 라인 사용이 권장됩니다. 이는 여러 운영 체제와 동일한 운영 체제의 다른 설치에서 작동하는 이식성 있는 shebang 라인을 지정할 수 있도록 해주기 때문입니다.

첫 번째 인수를 파일로 열 수 없거나 유효한 shebang 라인을 찾을 수 없는 경우, 런처는 '#!python' shebang 라인이 발견된 것처럼 작동합니다. 즉, 기본 Python 인터프리터를 찾아 인수를 전달합니다. 그러나 유효한 shebang 라인이 발견되었지만 해당 라인에 지정된 프로세스를 시작할 수 없는 경우, 기본 인터프리터는 시작되지 않습니다. 지정된 자식 프로세스를 생성하는 오류로 인해 런처는 적절한 메시지를 표시하고 특정 종료 코드와 함께 종료됩니다.

구성 파일 (Configuration file)

런처는 두 개의 .ini 파일을 검색합니다. 하나는 현재 사용자의 “애플리케이션 데이터” 디렉터리(예: Windows 함수 SHGetFolderPathCSIDL_LOCAL_APPDATA와 함께 호출하여 반환되는 디렉터리, Vista+에서는 %USERPROFILE%\AppData\Local, XP에서는 %USERPROFILE%\Local Settings\Application Data)에 있는 py.ini이고, 다른 하나는 런처와 동일한 디렉터리에 있는 py.ini입니다. 동일한 .ini 파일이 런처의 ‘콘솔’ 버전(즉, py.exe)과 ‘windows’ 버전(즉, pyw.exe) 모두에 사용됩니다.

“애플리케이션 디렉터리”에 지정된 사용자 지정은 실행 파일 옆에 있는 것보다 우선하므로, 런처 옆의 .ini 파일에 쓰기 권한이 없는 사용자도 해당 전역 .ini 파일의 명령을 재정의할 수 있습니다.

Shebang 라인의 가상 명령 (Virtual commands in shebang lines)

가상 명령(Virtual Commands)은 Unix 플랫폼에서 작동할 것으로 예상되는 문자열로 시작하는 shebang 라인입니다. 예를 들어, '/usr/bin/python', '/usr/bin/env python', 및 'python'이 있습니다. 선택적으로 가상 명령에는 /usr/bin/python2 또는 /usr/bin/python3.2와 같은 버전 한정자(아래 참조)가 접미사로 붙을 수 있습니다. 실행되는 명령은 아래 Python 버전 한정자에 설명된 규칙을 기반으로 합니다.

사용자 지정 명령 (Customized Commands)

런처는 Windows .ini 파일(즉, Windows 함수 GetPrivateProfileString으로 파싱할 수 있는 파일)에 “사용자 지정 명령(Customized Commands)”을 정의하는 기능을 지원합니다. [commands] 섹션을 생성하여 가상 명령을 정의하는 키 이름과 이 가상 명령에 사용될 실제 명령줄을 지정하는 값을 가질 수 있습니다.

예를 들어, INI 파일에 다음 내용이 있다면:

[commands]
vpython=c:\bin\vpython.exe -foo

스크립트 doit.py'#! vpython'이라는 shebang 라인이 있으면, 런처는 명령줄 c:\bin\vpython.exe -foo doit.py를 사용하게 됩니다.

.ini 파일의 이름, 위치 및 검색 순서에 대한 정확한 세부 정보는 런처 문서에 있습니다.

Python 버전 한정자 (Python Version Qualifiers)

설명된 일부 기능은 선택적 Python 버전 한정자를 사용할 수 있도록 합니다.

버전 한정자는 주 버전 번호로 시작하며, 선택적으로 마침표(‘.’)와 부 버전 지정자가 뒤따를 수 있습니다. 부 버전 한정자가 지정되면, 선택적으로 “-32”가 뒤따라 해당 버전의 32비트 구현이 사용됨을 나타낼 수 있습니다. 64비트 구현이 기본이므로 “-64” 한정자는 필요하지 않습니다.

동일한 (주.부) Python 버전의 32비트 및 64비트 구현이 모두 설치된 64비트 Windows에서는 64비트 버전이 항상 우선됩니다. 이는 런처의 32비트 및 64비트 구현 모두에 해당합니다. 32비트 런처는 사용 가능한 경우 지정된 버전의 64비트 Python 설치를 실행하는 것을 선호합니다. 이는 런처의 동작이 PC에 설치된 버전만으로 예측될 수 있도록 하고, 설치 순서에 관계없이 (즉, 32비트 또는 64비트 버전의 Python 및 해당 런처가 마지막으로 설치되었는지 알 필요 없이) 작동하기 위함입니다. 위에서 언급했듯이, 이 동작을 변경하기 위해 버전 지정자에 선택적 “-32” 접미사를 사용할 수 있습니다.

명령에서 버전 한정자가 발견되지 않으면, 환경 변수 PY_PYTHON을 설정하여 기본 버전 한정자를 지정할 수 있습니다. 기본값은 “2”입니다. 이 값은 주 버전만 (예: “2”) 또는 주.부 한정자 (예: “2.6”), 또는 주.부-32까지 지정할 수 있습니다.

부 버전 한정자가 발견되지 않으면, 환경 변수 PY_PYTHON{major} (여기서 {major}는 위에서 결정된 현재 주 버전 한정자임)를 설정하여 전체 버전을 지정할 수 있습니다. 그러한 옵션이 발견되지 않으면 런처는 설치된 Python 버전을 열거하고 주 버전에 대해 발견된 최신 부 릴리스를 사용합니다. 이는 해당 패밀리에서 가장 최근에 설치된 버전일 가능성이 높지만 보장되지는 않습니다.

환경 변수 외에도, 동일한 설정을 런처에서 사용하는 .INI 파일에 구성할 수 있습니다. INI 파일의 섹션은 [defaults]라고 불리며, 키 이름은 선행 PY_ 접두사가 없는 환경 변수와 동일합니다 (INI 파일의 키 이름은 대소문자를 구분하지 않습니다). 환경 변수의 내용은 INI 파일에 지정된 것을 재정의합니다.

명령줄 처리 (Command-line handling)

첫 번째 명령줄 인수가 ‘-‘로 시작하지 않는 경우에만 shebang 라인을 확인합니다.

유일한 명령줄 인수가 “-h” 또는 “–help”인 경우, 런처는 작은 배너와 명령줄 사용법을 인쇄한 다음 인수를 기본 Python으로 전달합니다. 이렇게 하면 런처에 대한 도움말이 인쇄된 다음 Python 자체에 대한 도움말이 인쇄됩니다. 런처의 출력은 확장된 도움말 정보가 Python이 아닌 런처에서 온 것임을 명확하게 나타낼 것입니다.

Python을 대화식으로 실행하는 편의를 위해, 런처는 위에서 설명한 대로 특정 버전을 지정하기 위해 첫 번째 명령줄 인수가 선택적으로 대시(“-“) 뒤에 버전 한정자를 포함하는 것을 지원합니다. 예를 들어, “py.exe”는 설치된 최신 Python 2.x 구현을 찾아서 실행할 수 있지만, “py.exe -3”과 같은 명령줄은 최신 Python 3.x 구현을 실행하도록 지정할 수 있습니다. “py.exe -2.6-32”는 32비트 구현 Python 2.6을 찾아 실행하도록 지정할 수 있습니다. -3 플래그로 Python 2.x 구현을 실행하려면 명령줄이 “py.exe -2 -3”과 유사해야 합니다 (또는 이 런처를 사용하지 않고 Python의 특정 버전을 수동으로 실행할 수 있습니다). 이 기능은 shebang 라인을 스캔하는 파일과 이 인수가 모두 첫 번째 인수여야 하므로 상호 배타적이어서 shebang 처리와 함께 사용할 수 없습니다.

다른 모든 인수는 자식 Python 프로세스에 변경되지 않고 전달됩니다.

프로세스 실행 (Process Launching)

런처는 대화식으로 작업하는 Python 개발자에게 몇 가지 편의를 제공합니다. 예를 들어, 명령줄 인수 없이 런처를 시작하면 명령줄 인수 없이 기본 Python을 실행합니다. 또한, 특정 Python 버전을 대화식으로 실행할 수 있도록 명령줄 인수를 지원할 것입니다. 그러나 이러한 편의 기능은 스크립트 실행이라는 주요 목적을 저해해서는 안 되며, 원한다면 쉽게 피할 수 있어야 합니다.

런처는 실제 인터프리터를 시작하기 위해 서브 프로세스(subprocess)를 생성합니다. 근거에 대해서는 아래 ‘논의 (Discussion)’를 참조하십시오.

논의 (Discussion)

런처가 System32 디렉터리가 아닌 Windows 디렉터리에 설치되는 것이 의외일 수 있습니다. 그 이유는 System32 디렉터리가 64비트 시스템에서 실행되는 32비트 프로세스의 Path에 없기 때문입니다. 그러나 Windows 디렉터리는 항상 Path에 있습니다.

Windows 디렉터리에 설치되는 런처는 32비트 실행 파일이므로 32비트 CPython 설치 프로그램이 32비트 및 64비트 Windows 설치 모두에 대해 동일한 바이너리를 제공할 수 있습니다.

이상적으로는 런처 프로세스가 동일한 프로세스 내에서 Python을 직접 실행할 것입니다. 이는 주로 런처 프로세스의 부모가 런처를 종료하고 Python 인터프리터를 종료시킬 수 있도록 하기 위함입니다. 런처가 Python을 서브 프로세스로 실행하고 런처의 부모가 런처를 종료하면 Python 프로세스는 영향을 받지 않습니다.

그러나 이 접근 방식과 관련된 여러 가지 실제적인 문제가 있습니다. Windows는 Unix의 execv* 계열 함수를 지원하지 않으므로, 런처가 Python DLL을 동적으로 로드해야만 가능하지만, 이는 여러 부작용을 초래할 것입니다. 가장 심각한 부작용은 sys.executable의 값이 Python 구현 대신 런처를 참조하게 된다는 것입니다. 많은 Python 스크립트가 sys.executable의 값을 사용하여 자식 프로세스를 시작하며, 런처가 사용되면 이러한 스크립트가 예상대로 작동하지 않을 수 있습니다. '#! /usr/bin/python3' shebang 라인을 가진 “부모” 스크립트가 sys.executable을 통해 (shebang 없는) 자식 스크립트를 시작하려고 시도하는 경우를 생각해 보십시오. 현재 자식은 부모 스크립트를 실행하는 것과 정확히 동일한 버전을 사용하여 시작됩니다. sys.executable이 런처를 참조하면 자식은 Python 2.x 버전을 사용하여 실행될 가능성이 높으며 SyntaxError로 실패할 가능성이 높습니다.

또 다른 장애물은 위에 설명된 “사용자 지정 명령” 기능을 사용하여 대체 Python 구현을 지원하는 것인데, 실행 중인 실행 파일에 명령을 동적으로 로드하는 것은 불가능합니다.

마지막 장애물은 64비트 및 32비트 프로그램에 관한 위의 규칙입니다. 32비트 런처는 64비트 버전의 Python을 로드할 수 없으며 그 반대도 마찬가지입니다.

이러한 고려 사항을 감안할 때, 런처는 자식 프로세스가 실행되는 동안 살아있는 상태로 자식 프로세스에서 명령을 실행한 다음, 자식에 의해 반환된 것과 동일한 종료 코드와 함께 종료됩니다. 런처 종료가 자식을 죽이지 않는다는 우려를 해결하기 위해 Win32 Job API가 사용되어 부모가 종료될 때 자식 프로세스가 자동으로 종료되도록 합니다 (해당 자식 프로세스의 자식은 현재와 마찬가지로 계속 실행됨). 이 Windows API는 Windows XP 이상에서 사용할 수 있으므로, 이 런처는 Windows 2000 이하에서는 작동하지 않습니다.

참조 (References)

이 문서는 퍼블릭 도메인에 공개되었습니다.


⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments