PEP 547 – -m 옵션을 사용한 확장 모듈 실행
- Author:
- Marcel Plch <gmarcel.plch at gmail.com>, Petr Viktorin <encukou at gmail.com>
- Status:
- Deferred
- Type:
- Standards Track
- Created:
- 25-May-2017
- Python-Version:
- 3.7
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
연기 공지
이 PEP의 가장 중요한 사용 사례이자 유일하게 명시된 사용 사례인 Cython은 아직 다단계 초기화를 지원할 준비가 되어 있지 않습니다. Cython은 전역 상태를 C 수준의 정적 변수에 보관합니다. Cython issue 1923의 논의를 참고하십시오.
이 PEP는 상황이 바뀔 때까지 연기됩니다.
초록
이 PEP는 PEP 489 다단계 초기화를 사용하여 내장 모듈과 확장 모듈을 __main__ 네임스페이스에서 실행할 수 있도록 하는 구현을 제안합니다.
이를 통해 다단계 초기화가 활성화된 모듈을 다음 명령으로 실행할 수 있습니다:
$ python3 -m _testmultiphase
This is a test module named __main__.
동기
현재 확장 모듈은 Python 소스 모듈의 모든 기능을 지원하지는 않습니다. 구체적으로 말하면, Python의 -m 옵션을 사용하여 확장 모듈을 스크립트로 실행할 수 없습니다.
이를 가능하게 하는 기술적 기반 작업은 PEP 489를 위해 이미 이루어졌으며, -m 옵션을 활성화하는 일은 해당 PEP의 “향후 가능한 확장(Possible Future Extensions)” 절에 올라 있습니다. 기술적으로 여기에서 제안하는 추가 변경 사항은 비교적 작습니다.
근거
확장 모듈이 -m 옵션을 지원하지 않는 문제는 전통적으로 Python 래퍼를 제공하는 방식으로 우회해 왔습니다. 예를 들어, _pickle 모듈의 명령줄 인터페이스는 순수 Python으로 작성된 pickle 모듈에 있습니다(순수 Python 재구현도 함께 들어 있습니다).
C API를 사용하여 명령줄 인터페이스를 만드는 것은 번거롭기 때문에, 이 방식은 표준 라이브러리 모듈에는 잘 맞습니다. 하지만 다른 사용자들은 실행 가능한 확장 모듈을 직접 만들고 싶어 할 수 있습니다.
중요한 사용 사례는 C 확장 모듈로 컴파일되는 Python과 유사한 언어인 Cython입니다. Cython은 Python의 (거의) 상위 집합입니다. 즉, Python 모듈을 Cython으로 컴파일해도 일반적으로 모듈의 기능이 바뀌지 않으므로 Cython 고유의 기능을 점진적으로 추가할 수 있습니다. 이 PEP를 통해 Cython 확장 모듈은 -m 옵션으로 실행될 때 대응하는 Python 모듈과 똑같이 동작할 수 있게 됩니다. Cython 개발자들은 이 기능이 구현할 가치가 있다고 봅니다(Cython issue 1715를 참고하십시오).
배경
Python의 -m 옵션은 runpy._run_module_as_main 함수가 처리합니다.
-m으로 지정된 모듈은 일반적인 방식으로 임포트되지 않습니다. 대신 인터프리터 초기화 과정의 꽤 이른 시점에 생성되는 __main__ 모듈의 네임스페이스에서 실행됩니다.
Python 소스 모듈의 경우 다른 모듈의 네임스페이스에서 실행되어도 문제가 되지 않습니다. 코드는 locals와 globals를 기존 모듈의 __dict__로 설정한 상태로 실행되기 때문입니다. 확장 모듈은 그렇지 않습니다. 확장 모듈의 PyInit_* 진입점은 전통적으로 (PyModule_Create를 사용하여) 새 모듈 객체를 생성하는 일과 그 객체를 초기화하는 일을 모두 수행했습니다.
Python 3.5부터 확장 모듈은 PEP 489 다단계 초기화를 사용할 수 있습니다. 이 경우 PyInit_* 진입점은 PyModuleDef 구조체를 반환합니다. 이 구조체는 모듈을 어떻게 생성하고 초기화해야 하는지를 기술합니다. 확장은 Py_mod_create 콜백을 사용하여 모듈 객체 생성을 사용자 정의할 수도 있고, Py_mod_create를 지정하지 않음으로써 일반 모듈 객체를 사용하도록 선택할 수도 있습니다. 그런 다음 또 다른 콜백인 Py_mod_exec가 호출되어 모듈 객체를 초기화합니다. 예를 들어 모듈 객체를 메서드와 클래스로 채웁니다.
제안
다단계 초기화를 사용하면 확장 모듈을 다른 모듈의 네임스페이스에서 실행할 수 있습니다. Py_mod_create 콜백이 지정되지 않은 경우, __main__ 모듈을 Py_mod_exec 콜백에 전달하여 초기화할 수 있으며, 이때 __main__은 마치 새로 생성된 모듈 객체인 것처럼 취급됩니다.
이 방식에서 한 가지 복잡한 점은 C 수준의 모듈 상태입니다. 각 모듈에는 확장 모듈이 생성될 때 할당되는 메모리 영역을 가리키는 md_state 포인터가 있습니다. 할당할 메모리의 양은 PyModuleDef가 지정합니다.
구현은 md_state 메모리가 최대 한 번만 할당되도록 주의해야 합니다. 또한 Py_mod_exec 콜백은 모듈당 한 번만 호출되어야 합니다. 여러 번 초기화된 모듈이 초래하는 결과는 너무 미묘해서, 확장 작성자에게 이를 따져 보도록 요구할 수 없습니다. md_state 포인터 자체가 보호 장치 역할을 합니다. 메모리 할당과 Py_mod_exec 호출은 항상 함께 수행되며, md_state가 이미 NULL이 아니면 확장 모듈의 초기화는 실패합니다.
__main__ 모듈은 확장 모듈로 생성되지 않으므로, 그 md_state는 보통 NULL입니다. __main__의 컨텍스트에서 확장 모듈을 초기화하기 전에, 그 모듈의 PyModuleDef에 따라 모듈 상태가 할당됩니다.
PEP 489는 이러한 변경이 전반적으로 가능하도록 설계되었지만, 새로 초기화된 모듈 대신 다른 모듈을 사용할 수 있도록 확장 모듈의 모듈 탐색, 생성, 초기화 단계를 분리해야 하며, 이 기능을 runpy와 importlib에 추가해야 합니다.
명세
importlib 로더에 새로운 선택적 메서드가 추가됩니다. 이 메서드의 이름은 exec_in_module이며, 모듈 스펙과 이미 존재하는 모듈이라는 두 개의 위치 인자를 받습니다. 모듈에 이미 설정되어 있는 __spec__이나 __name__ 같은 임포트 관련 속성은 무시됩니다.
runpy._run_module_as_main 함수는 이 새로운 로더 메서드를 찾습니다. 이 메서드가 있으면 runpy는 모듈의 Python 코드를 로드하고 실행하려고 시도하는 대신 이 메서드를 실행합니다. 그렇지 않으면 runpy는 이전과 같이 동작합니다.
ExtensionFileLoader 변경 사항
importlib의 ExtensionFileLoader에는 새로운 함수인 _imp.exec_in_module을 호출하는 exec_in_module 구현이 추가됩니다.
_imp.exec_in_module은 기존 메커니즘을 사용하여 확장 모듈의 PyInit_* 함수를 찾아 호출합니다.
PyInit_* 함수는 완전히 초기화된 모듈(단일 단계 초기화) 또는 PEP 489 다단계 초기화를 위한 PyModuleDef를 반환할 수 있습니다.
단일 단계 초기화의 경우 _imp.exec_in_module은 ImportError를 발생시킵니다.
다단계 초기화의 경우 PyModuleDef와 초기화할 모듈이 새로운 함수인 PyModule_ExecInModule에 전달됩니다.
이 함수는 PyModuleDef가 Py_mod_create 슬롯을 지정하거나, 모듈이 이미 초기화된 경우(즉, 모듈의 md_state 포인터가 NULL이 아닌 경우) ImportError를 발생시킵니다. 그렇지 않으면 이 함수는 PyModuleDef에 따라 모듈을 초기화합니다.
하위 호환성
이 PEP는 하위 호환성을 유지합니다. 이 PEP는 새 함수를 추가하고, 이전에는 모듈을 __main__으로 실행하는 것을 지원하지 않던 로더에 새 로더 메서드를 추가할 뿐입니다.
참조 구현
이 PEP의 참조 구현은 GitHub에서 확인할 수 있습니다.
참고 문헌
Copyright
This document has been placed in the public domain.