PEP 648 – 인터프리터 시작 시의 확장 가능한 사용자 정의
- Author:
- Mario Corchero <mariocj89 at gmail.com>
- Sponsor:
- Pablo Galindo Salgado
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 30-Dec-2020
- Python-Version:
- 3.11
- Post-History:
- 16-Dec-2020, 18-Dec-2020
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 사용자가 시작 시 실행될 파일을 설치할 수 있도록 하여 인터프리터의 확장 가능한 사용자 정의를 지원할 것을 제안합니다.
PEP 거부
PEP 648은 사용 사례가 제한적이고 시작 과정을 더욱 복잡하게 만든다는 이유로 운영 위원회에 의해 거부되었습니다.
동기
시스템 관리자, 인터프리터를 재패키징하는 도구, 그리고 일부 라이브러리는 시작 시점에 인터프리터의 여러 측면을 사용자 정의해야 합니다.
시스템 관리자는 보통 sitecustomize.py를 통해 이를 달성하는 반면, 라이브러리는 pth 파일을 편법으로 활용하는 방식에 의존합니다. 이 PEP는 동일한 기능을 더 사용자 친화적이고 구조화된 방식으로 달성하는 방법을 제안합니다.
pth 파일의 한계
라이브러리가 임포트 전에 어떤 사용자 정의를 수행해야 하거나 인터프리터의 전반적인 동작과 관련된 사용자 정의를 수행해야 할 때, 시작 시점에 로드되며 site 모듈 [7]을 통해 구현되는 pth 파일이 Python 코드를 포함할 수 있고 그 코드가 pth 파일이 평가될 때 실행된다는 사실에 의존하는 경우가 많습니다.
pth 파일은 원래 sys.path에 추가 디렉터리를 더하기 위한 용도로만 개발되었지만, “import”로 시작하는 줄을 포함할 수도 있으며 이러한 줄은 exec()에 전달된다는 점에 유의하십시오. 사용자들은 필요한 사용자 정의를 가능하게 하려고 이 기능을 편법으로 활용해 왔습니다. 예시로 setuptools [4] 또는 betterexceptions [5]를 참고하십시오.
이러한 목적으로 pth 파일을 사용하는 것은 라이브러리 개발자에게 전혀 이상적이지 않습니다. import가 앞에 오는 단 한 줄에 코드를 주입해야 하므로 가독성이 상당히 떨어지기 때문입니다. 이러한 관행을 따르는 라이브러리 개발자는 betterexceptions [5]가 그러하듯 보통 임포트 시 모든 작업을 수행하는 모듈을 만들지만, 이 방식은 여전히 그다지 사용자 친화적이지 않습니다.
또한 Python 시작 시점에 무엇이 실행되는지 살펴보려는 인터프리터 사용자에게도 바람직하지 않습니다. 모든 site 경로에 흩어져 있을 수 있는 pth 파일을 전부 검토하여 코드가 실행될 가능성이 있는지 확인해야 하기 때문입니다. 이러한 pth 파일 대부분은 경로만 수정하는 “정상적인” pth 파일일 것이므로, “무엇이 시작 시 내 인터프리터를 변경하고 있는가”라는 질문에 답하기가 상당히 복잡해집니다.
마지막으로, pth 파일에서 코드 실행 기능을 제거하자는 제안이 여러 차례 있었습니다. [1]와 [2]를 참고하십시오.
sitecustomize.py의 한계
sitecustomize는 수용할 만한 해결책이지만, 한 사람이 시스템과 인터프리터를 모두 책임진다고 가정합니다. 시스템 관리자와 인터프리터 제공 담당자가 모두 인터프리터 시작 시 사용자 정의를 추가하려는 경우, 이들은 파일 내용에 합의하고 모든 변경 사항을 합쳐야 합니다. 하지만 이는 큰 한계가 아니며, 이 변경의 주된 동기도 아닙니다. 이 변경이 이루어진다면 이러한 사용자들의 상황도 개선될 것입니다. 모든 작업을 수행하는 sitecustomize.py 하나를 두는 대신, 개선하려는 기능의 이름을 딴 독립된 사용자 정의 파일들을 둘 수 있기 때문입니다. 예를 들어, Ubuntu는 현재의 sitecustomize.py를 단순히 ubuntu_apport_python_hook으로 바꿀 수 있습니다. 이는 그 의도를 더 잘 나타낼 뿐만 아니라, 인터프리터 사용자가 자신의 인터프리터에서 일어나는 수정 사항을 더 잘 이해할 수 있게 해 줍니다.
근거
이 PEP는 시작 시점에 sitepackages [8] 또는 usersitepackages [9]에 있는 __sitecustomize__라는 이름의 디렉터리에서 발견된 모든 파일을 실행하여, 시작 시 인터프리터의 확장 가능한 사용자 정의를 지원할 것을 제안합니다.
왜 __sitecustomize__인가
이 이름은 이미 존재하는 sitecustomize.py 개념을 따르는 것을 목표로 합니다. 이 디렉터리는 site 경로에 위치하므로 sys.path 안에 있게 됩니다. 따라서 이미 존재하는 sitecustomize.py와 충돌하지 않도록 이름 앞뒤에 이중 밑줄을 사용하기로 했습니다.
새로운 __sitecustomize__ 디렉터리 탐색
Python 인터프리터는 시작 시 표준 사이트 패키지(site-packages) 경로 중 어느 곳에서든 __sitecustomize__라는 이름의 디렉터리를 찾습니다.
이러한 경로는 일반적으로 Python 시스템 위치와 사용자 위치이지만, 최종적으로는 site 모듈의 로직에 의해 정의됩니다.
사용자는 site.sitepackages [8]와 site.usersitepackages [9]를 사용하여 인터프리터가 __sitecustomize__ 디렉터리를 발견할 수 있는 경로를 알 수 있습니다.
__sitecustomize__ 발견 시점
__sitecustomize__ 디렉터리는 site.addsitedir [10] 과정의 일부로 사이트 패키지(site-packages) 경로에서 pth 파일이 발견된 직후에 발견됩니다.
이 과정은 각 사이트 패키지(site-packages) 경로마다, 현재 pth 파일에 적용되는 것과 정확히 같은 순서로 반복됩니다.
__sitecustomize__ 내부의 실행 순서
구현은 각 __sitecustomize__ 디렉터리를 발견할 때 __sitecustomize__ 안의 파일들을 이름순으로 정렬하여 실행합니다. 다만 사용자가 실행 순서에 의존하는 것은 권장하지 않습니다.
파일을 무작위 순서로 실행하는 방안도 검토했지만, 그렇게 하면 인터프리터가 파일을 어떻게 선택하느냐에 따라 결과가 달라질 수 있습니다. 따라서 다른 파일의 실행에 의존하는 것이 좋은 관행은 아니더라도, 인터프리터 시작 시 매번 무작위로 다른 결과가 나오는 것보다는 낫다고 생각합니다. 사용자가 파일을 실행하기 전에 경로에 항목을 추가해야 하는 경우를 고려하여 pth 파일 다음에 파일을 실행하기로 했습니다.
pth 파일과의 상호 작용
pth 파일을 사용하여 sys.path에 경로를 추가할 수 있지만, 이는 __sitecustomize__ 발견 과정에 영향을 주어서는 안 됩니다. 해당 디렉터리는 오직 사이트 패키지(site-packages) 경로에서만 찾기 때문입니다.
__sitecustomize__ 내부 파일의 실행
__sitecustomize__ 디렉터리가 발견되면, 그 안에 있는 .py 확장자를 가진 모든 파일을 io.open_code로 읽고 exec [11]를 사용하여 실행합니다.
서로 다른 파일 간의 예기치 않은 상호 작용을 방지하기 위해 exec 함수에 globals로 빈 딕셔너리를 전달합니다.
실패 처리
어느 파일이든 실행 중 발생한 오류는 인터프리터가 상세(verbose) 모드로 실행되지 않는 한 기록되지 않으며, 다른 파일의 평가를 중단시켜서도 안 됩니다. 사용자는 파일 실행에 실패했다는 것과 상세 모드를 사용하여 더 많은 정보를 얻을 수 있다는 메시지를 stderr로 받게 됩니다. 이 동작은 sitecustomize.py에 대해 존재하는 기존 동작을 모방한 것입니다.
가상 환경과의 상호 작용
새로운 __sitecustomize__ 방식을 통해 인터프리터에 적용된 사용자 정의는, sitecustomize.py가 가상 환경과 상호 작용하는 것과 같은 방식으로 사용자가 가상 환경을 만들 때에도 계속 동작합니다.
이는 include-system-site-packages가 활성화되지 않는 한 가상 환경으로 전파되지 않는 pth 파일과 비교했을 때의 차이점입니다.
라이브러리 유지 관리자가 __sitecustomize__를 통해 설치한 기능 중 가상 환경으로 전파되기를 원하지 않는 기능이 있다면, sys.prefix == sys.base_prefix를 확인하여 가상 환경 안에서 실행 중인지 감지해야 합니다. 이 동작은 전역 sitecustomize.py를 수정하는 패키지와 유사합니다.
sitecustomize.py 및 usercustomize.py와의 상호 작용
제거되기 전까지 sitecustomize와 usercustomize는 pth 파일과 마찬가지로 __sitecustomize__ 다음에 실행됩니다. sitecustomize와 usercustomize의 제거 계획에 관한 정보는 하위 호환성 섹션을 참조하십시오.
설치된 모든 파일 식별하기
Python 시작 과정의 디버깅을 쉽게 하기 위해, site 모듈이 호출되면 시작 시점에 발견될 __sitecustomize__ 디렉터리를 출력합니다.
파일 명명 규칙
패키지 간 충돌을 피하기 위해 패키지는 파일 이름에 패키지 이름을 포함하도록 권장됩니다. 다만 파일 이름에 대한 유일한 요구 사항은 인터프리터가 실행할 수 있도록 .py로 끝나야 한다는 것입니다.
시작 파일 비활성화
시작 시간이 중요한 경우처럼 일부 상황에서는 이 옵션을 완전히 비활성화하는 것이 바람직할 수 있습니다. 이미 존재하는 플래그 -S [3]는 이 새로운 기능을 포함하여 site 관련 조작을 모두 비활성화합니다. 이 플래그가 전달되면 __sitecustomize__ 디렉터리를 탐색하지 않습니다.
또한 이 새로운 기능만 비활성화한 채로 인터프리터를 시작할 수 있도록 -X 아래에 새로운 옵션 disablesitecustomize가 추가되며, 이 옵션은 __sitecustomize__ 탐색만을 비활성화합니다.
마지막으로, 사용자는 site.py 모듈이 제공하는 여러 옵션 중 하나로 사용자 사이트를 비활성화하여 사용자 사이트에서만 __sitecustomize__ 디렉터리 탐색을 비활성화할 수 있습니다.
빌드 백엔드에서의 지원
빌드 백엔드는 이러한 파일을 __sitecustomize__ 디렉터리에 쉽게 설치할 수 있도록 하는 옵션을 제공하기로 선택할 수 있지만, 이 PEP는 이를 직접 다루지 않습니다. pth 파일과 마찬가지로, 빌드 백엔드는 __sitecustomize__ 파일을 위한 손쉬운 설정 메커니즘을 제공하지 않고, 사용자가 설치 과정에 훅을 걸어 이러한 파일을 포함하도록 할 수도 있습니다. 저희는 빌드 백엔드의 향상된 지원을 이 PEP의 요구 사항으로 보지 않습니다.
시작 시간에 미치는 영향
이 구현에서 우려되는 점은 이 추가 사항으로 인해 Python 인터프리터의 시작 시간이 어떤 영향을 받을 수 있는가 하는 것입니다. 저희는 성능 영향이 테스트 대상 Python 환경에 사용자나 시스템 관리자가 설치하는 파일의 로직과 밀접하게 연관될 것으로 예상합니다.
인터프리터의 __sitecustomize__ 디렉터리에 파일이 하나라도 있으면, 파일 실행 시간과 코드를 읽는 호출 시간이 시작 시간에 추가됩니다. 이는 sitecustomize.py, usercustomize.py 및 pth 파일의 코드를 통한 코드 실행이 시작 시간에 영향을 미치는 방식과 유사합니다. 따라서 여기서는 이 해결책을 이 세 가지와 비교하는 데 집중하겠습니다. 그렇지 않으면 시작 시간에 실제로 추가되는 시간은 해당 파일에서 실행되는 코드에 크게 좌우되기 때문입니다.
결과는 perf로 “./python.exe -c pass”를 50회 반복 실행하되, 각 반복마다 명령을 50번 되풀이하고 모든 결과의 기하 평균을 구하는 방식으로 수집했습니다. 이 벤치마크를 실행하는 데 사용한 파일은 참조 구현 [6]에 체크인되어 있습니다.
벤치마크는 PGO와 LTO로 컴파일된 3.10 알파 7을 사용하여 다음 매개변수와 시스템 상태에서 실행되었습니다:
- Perf 이벤트: 최대 샘플링 속도를 초당 1회로 설정
- CPU 주파수: CPU 17,35의 최소 주파수를 최대 주파수로 설정
- Turbo Boost (MSR): CPU 17에서 Turbo Boost 비활성화: MSR 0x1a0을 0x4000850089로 설정
- IRQ 선호도: 기본 선호도를 CPU 0-16,18-34로 설정
- IRQ 선호도: IRQ 1,3-16,21,25-31,56-59,68-85,87,89-90,92-93,95-104의 선호도를 CPU 0-16,18-34로 설정
- CPU: 논리 CPU 2개 사용: 17,35
- Perf 이벤트: 최대 샘플링 속도: 초당 1회
- ASLR: 완전 무작위화
- Linux 스케줄러: 격리된 CPU (2/36): 17,35
- Linux 스케줄러: RCU가 비활성화된 CPU (2/36): 17,35
- CPU 주파수: 0-16,18-34=min=1200 MHz, max=3600 MHz; 17,35=min=max=3600 MHz
- Turbo Boost (MSR): CPU 17,35: disabled
pth 파일, sitecustomize.py, usercustomize.py, 그리고 __sitecustomize__ 안의 파일에서 실행되도록 배치한 코드는 다음과 같습니다:
import time; x = time.time() ** 5
이 파일은 간단한 연산을 실행하되, 그 비용은 여전히 무시할 수 있을 정도가 되도록 의도한 것입니다. 이는 실험을 비교적 현실적으로 유지하면서도, 이 메커니즘으로 인한 성능 저하가 있다면 드러나도록 하기 위한 것입니다. 또한 pth 파일에서도 사용할 수 있도록 import로 시작하며 한 줄로 되어 있습니다.
| Test | # of files | Time (us) | ||||
|---|---|---|---|---|---|---|
| # | sitecustomize.py |
usercustomize.py |
pth |
__sitecustomize__ |
Run 1 | Run 2 |
| 1 | 0 | 0 | 0 | Dir not created | 13884 | 13897 |
| 2 | 0 | 0 | 0 | 0 | 13871 | 13818 |
| 3 | 0 | 0 | 1 | 0 | 13964 | 13924 |
| 4 | 0 | 0 | 0 | 1 | 13940 | 13939 |
| 5 | 1 | 1 | 0 | 0 | 13990 | 13993 |
| 6 | 0 | 0 | 0 | 2 (system + user) | 14063 | 14040 |
| 7 | 0 | 0 | 50 | 0 | 16011 | 16014 |
| 8 | 0 | 0 | 0 | 50 | 15456 | 15448 |
결과는 참조 구현 [6]에서 제공하는 run-benchmark.py 스크립트로 재현할 수 있습니다.
이 결과로부터 다음과 같이 해석합니다:
sitecustomize.py와usercustomize.py를 사용하는 것과 비교하면__sitecustomize__스크립트 두 개를 사용할 때 인터프리터가 0.3% 느려집니다. 사용자가 파일을 만들지 않더라도 인터프리터는 여전히 이 파일들을 임포트하려고 시도하므로, 향후 릴리스에서sitecustomize.py와usercustomize.py가 제거될 때까지는 이러한 속도 저하가 있을 것으로 예상합니다.- 코드를 포함한 임의의 pth 파일 50개로 테스트한 결과, 이를
__sitecustomize__로 옮기면 시작 속도가 약 3.5% 빨라집니다. 이는pth파일 실행에 비해__sitecustomize__파일을 평가하는 로직이 더 단순하기 때문일 가능성이 높습니다. - 전반적으로 모든 측정 결과는 이 추가 기능이 시작 시간에 미치는 영향이 작다는 것을 보여 줍니다.
감사 이벤트
보안 검사를 용이하게 하기 위해 새로운 감사 이벤트가 추가되며, __sitecustomize__ 실행 시 “sitecustimze.exec_file”을 이름으로, 파일 이름을 인자로 하여 sys.audit [12]를 호출함으로써 발생합니다.
보안 영향
이 PEP는 모든 코드 실행을 pth 파일에서 __sitecustomize__ 디렉터리 안의 파일로 옮기는 것을 목표로 합니다. 다음과 같은 이유로 이것이 시스템 관리자에게 개선이 된다고 생각합니다:
- 모든
pth파일을 살펴볼 필요 없이 하나의 디렉터리만 확인하여 인터프리터가 시작 시점에 실행하는 코드를 빠르게 파악할 수 있습니다. - 새로 제안된 감사 이벤트를 통해 이 기능의 사용을 추적할 수 있습니다.
__sitecustomize__디렉터리의 권한을 조정할 수 있게 하여 더 세밀한 제어를 제공하며, 잠재적으로 사용자가 인터프리터 시작 과정을 변경하지 않는 패키지만 설치하도록 할 수 있습니다.
요컨대, 이 방식은 악의적인 사용자가 시작 시점에 실행될 파일을 배치하는 것을 허용하기는 하지만, 기존 pth 파일에 비하면 개선된 것입니다.
교육 방법
인터프리터가 시작할 때 site 경로에서 __sitecustomize__ 디렉터리를 찾으려 시도하며, .py 확장자를 가진 파일을 발견하면 하나씩 실행한다고 설명하는 것만으로 간단히 문서화하고 가르칠 수 있습니다.
시스템 관리자와 인터프리터를 패키지로 만드는 도구에게는, 이제 예전에 sitecustomize.py를 두던 것처럼 __sitecustomize__에 파일을 두도록 권장할 수 있습니다. 커스터마이즈하려는 로직을 처리하는 별도의 파일을 제공할 수 있으므로, 다음 사람이 자신의 내용을 덮어쓰지 않을 것이라고 더 안심할 수 있습니다.
라이브러리 개발자는 setuptools 같은 도구에서 이러한 새 파일을 주입하는 새로운 인자를 지정할 수 있어야 합니다. 예를 들어 sitecustomize_files=["scripts/betterexceptions.py"]와 같은 형태로 지정하면 해당 파일들을 추가할 수 있습니다. 빌드 백엔드가 이를 지원하지 않는다면, 기존에 pth 파일에서 하던 것처럼 수동으로 설치할 수 있습니다. 파일 이름에 패키지 이름을 포함하도록 권장할 것입니다.
하위 호환성
이 PEP는 3.11, 3.12, 3.13에서 sitecustomize.py, usercustomize.py, pth 코드 실행에 폐지 예정 경고를 추가합니다. 이 기능들은 3.14까지 제거할 계획입니다. 이상적으로는 로직을 다른 파일로 옮기기만 하면 이러한 방식에서 __sitecustomize__로 이전할 수 있어야 합니다.
기존 sitecustomize.py 메커니즘은 이 파일을 사이트 경로에 두는 시스템 관리자를 대상으로 만들어졌지만, 실제로는 인터프리터가 시작될 때 경로상의 어느 곳에나 이 파일을 둘 수 있었습니다. 새 메커니즘에서는 사용자가 __sitecustomize__ 디렉터리를 경로상의 아무 곳에나 둘 수 없으며, 사이트 경로에만 둘 수 있습니다. 시스템 관리자는 이전 방법으로 sitecustomize를 임포트하기만 하는 사용자 정의 파일을 __sitecustomize__에 추가하여 sitecustomize.py와 비슷한 동작을 되살릴 수 있습니다.
참조 구현
CPython 테스트 스위트를 통과하는 초기 구현을 평가용으로 이용할 수 있습니다 [6].
이 구현은 검토자가 직접 사용해 보면서 이 PEP가 일으킬 수 있는 잠재적 문제를 확인하는 용도일 뿐입니다.
기각된 아이디어
아무것도 하지 않기
현재 상태도 “동작”하기는 하지만, 동기 절에서 나열한 문제들이 있습니다. 이 변경의 영향을 분석한 결과, 이 변경이 가져오는 향상된 경험을 고려하면 그만한 가치가 있다고 판단합니다.
pth 파일 사용을 공식화하기
또 다른 방법은 시작 시 코드를 주입하는 데 pth 파일을 사용하는 방식을 그대로 격상하여 문서화하는 것이지만, 동기 절에서 나열했듯이 이는 사용자에게 최선의 경험이 아닙니다.
__sitecustomize__를 네임스페이스 패키지로 만들기
이 디렉터리를 네임스페이스 패키지로 만들고 그 안의 모든 모듈을 임포트하는 방안도 검토했습니다. 이렇게 하면 초기화 시점에 sys.path의 모든 경로를 검색할 수 있고, 파일끼리 서로 임포트하여 파일 간 의존성을 선언하는 방법도 생깁니다. 이 방안은 여러 이유로 기각되었습니다:
- 임의의 파일이 실행되는 경로 목록을 불필요하게 넓힙니다.
- 패키지가 이 위치 중 하나에
__init__.py파일을 설치하면 어떻게 할지와 같은 추가적인 복잡성이 생깁니다. - 이미 사이트 경로에서
pth파일을 찾고 있으므로, 네임스페이스 패키지를 실제로 임포트하는 것보다__sitecustomize__를 검색하는 편이 비용이 적게 듭니다.
종료 시 사용자 정의 지원
init.d 사용자는 종료 시점에도 코드를 추가할 수 있는 방식으로 이 기능을 구현하고 싶을 수 있지만, Python 사용자는 이미 atexit를 통해 그렇게 할 수 있으므로 별도의 지원은 필요하지 않습니다.
entry_points 사용하기
시작 시 실행할 파일을 지정할 수 있도록 진입점(entry point)의 사용 범위를 넓히는 방안도 검토했지만, 두 가지 주요 이유로 이 방안을 폐기했습니다. 첫 번째 이유는 시작 시간에 미치는 영향입니다. 이 방식에서는 파일 몇 개를 실행하기 위해 모든 패키지의 배포 정보를 탐색해야 합니다. 이는 사용자가 이 기능을 사용하지 않더라도 성능에 영향을 주며, 그 영향은 환경에 설치된 패키지 수에 비례하여 커집니다. 두 번째 이유는 이 PEP에서 제안하는 구현이 패키지와 시스템 관리자 모두에게 하나의 시작 사용자 정의 방법을 제공한다는 점입니다. 또한 진입점의 주된 목적이 라이브러리가 시작 시 실행될 파일을 쉽게 설치하게 하는 것이라면, 그런 기능은 여전히 추가할 수 있으며 빌드 백엔드가 파일을 __sitecustomize__ 디렉터리 안에 설치하기만 하면 됩니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Acknowledgements
Thanks Pablo Galindo for contributing to this PEP and offering his PC to run the benchmark.