PEP 304 – 바이트코드 파일 생성 제어
- Author:
- Skip Montanaro
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 22-Jan-2003
- Post-History:
- 27-Jan-2003, 31-Jan-2003, 17-Jun-2005
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
역사적 참고 사항
이 원래 PEP는 철회되었지만, 이 기능의 변형이 결국 Python 3.8에서 https://bugs.python.org/issue33499 를 통해 구현되었습니다.
이 PEP에서 처음 제기된 여러 문제와 우려 사항은 그 후 몇 년 동안 이루어진 다른 변경을 통해 해결되었습니다:
- 잠재적인 보안 우려를 처리하기 위한 격리 모드(isolated mode)의 도입
- 완전히 임포트 훅 기반으로 이루어진 임포트 시스템 구현인
importlib로의 전환 __pycache__하위 디렉터리를 사용하도록 바이트코드 캐시 배치를 바꾼 PEP 3147의 변경. 여기에는 인터프리터가 별도의 캐시 디렉터리로의 리디렉션을 자동으로 처리할 수 있게 하는source_to_cache(path)및cache_to_source(path)API가 포함됩니다.
초록
이 PEP는 컴파일된 Python 바이트코드 파일의 생성과 위치를 제어하는 메커니즘을 개괄합니다. 이 아이디어는 원래 패치 요청 [1]에서 시작되었으며, python-dev 메일링 리스트의 토론 스레드 [2]로 발전했습니다. 환경 변수를 도입하면 Python이나 Python 기반 서드파티 패키지를 설치하는 사람들이 설치 시점에 바이트코드 파일을 생성할지 여부와, 생성한다면 어디에 기록할지를 제어할 수 있게 됩니다. 또한 사용자가 애플리케이션 실행 시점에 바이트코드 파일을 생성할지 여부와, 생성한다면 어디에 기록할지를 제어할 수 있게 됩니다.
제안
Python이 인식하는 환경 변수 목록에 새로운 환경 변수 PYTHONBYTECODEBASE를 추가합니다. PYTHONBYTECODEBASE는 다음과 같이 해석됩니다:
- 정의되지 않은 경우, Python 바이트코드는 현재와 정확히 같은 방식으로 생성됩니다. sys.bytecodebase는 루트 디렉터리(Unix와 Mac OSX에서는 /, Windows에서는 시작(설치???) 드라이브의 루트 디렉터리 – 일반적으로
C:\–)로 설정됩니다. - 정의되어 있고 사용자에게 쓰기 권한이 있는 기존 디렉터리를 가리키는 경우, sys.bytecodebase는 해당 디렉터리로 설정되며 바이트코드 파일은 그 위치를 루트로 하는 디렉터리 구조에 기록됩니다.
- 정의되어 있지만 비어 있는 경우, sys.bytecodebase는 None으로 설정되며 바이트코드 파일 생성이 완전히 억제됩니다.
- 정의되어 있고 다음 중 하나에 해당하는 경우:
- 디렉터리를 가리키지 않는 경우,
- 디렉터리를 가리키지만 사용자에게 쓰기 권한이 없는 디렉터리인 경우
경고가 표시되고, sys.bytecodebase는 None으로 설정되며 바이트코드 파일 생성이 완전히 억제됩니다.
시작 초기화 이후의 모든 런타임 참조는 PYTHONBYTECODEBASE 환경 변수가 아니라 sys.bytecodebase를 대상으로 합니다. sys.path는 수정되지 않습니다.
위 내용으로부터 sys.bytecodebase가 None 또는 시스템의 유효한 디렉터리를 가리키는 문자열이라는 두 가지 유효한 유형의 값만 가질 수 있음을 알 수 있습니다.
임포트 중에 이 확장은 다음과 같이 동작합니다:
- 모듈에 대한 일반적인 검색이 수행됩니다. 검색 순서는 대략 동적으로 로드되는 확장 모듈, Python 소스 파일, Python 바이트코드 파일 순입니다. 이 메커니즘은 Python 소스 파일이 발견된 경우에만 작동합니다.
- 소스 모듈을 찾으면 같은 디렉터리에서 바이트 컴파일된 파일을 읽으려고 시도합니다. (이는 이전과 같습니다.)
- 바이트 컴파일된 파일이 발견되지 않으면, 확장된 디렉터리에서 바이트 컴파일된 파일을 읽으려고 시도합니다.
- 바이트코드 생성이 필요한 경우, 생성된 바이트코드는 가능하다면 확장된 디렉터리에 기록됩니다.
이 PEP는 바이트코드 파일의 처리를 모듈 단위나 디렉터리 단위로 제어하는 기능을 제공하는 것에 관한 것이 명시적으로 아님에 유의하십시오.
용어집
- “바이트코드 베이스(bytecode base)”는 sys.bytecodebase의 현재 설정값을 가리킵니다.
- “확장 디렉터리(augmented directory)”는 바이트코드 베이스와 소스 파일의 디렉터리 이름을 조합해 만든 디렉터리를 가리킵니다.
- PYTHONBYTECODEBASE는 “바이트코드 베이스”와 구별해야 할 때 환경 변수를 가리키는 말로 사용합니다.
바이트코드 파일 찾기
인터프리터는 모듈을 검색할 때 평소처럼 sys.path를 사용합니다. 그러나 바이트코드 파일이 후보로 고려될 때에는 바이트코드 파일을 한 번 더 탐색할 수 있습니다. 먼저 sys.path에서 소스 파일이 있는 디렉터리를 사용해 바이트코드 파일을 확인합니다(현재 동작). 그곳에서 유효한 바이트코드 파일을 찾지 못했고(파일이 없거나, 있더라도 최신 상태가 아닌 경우) 바이트코드 베이스가 None이 아니면, sys.path의 디렉터리 앞에 바이트코드 베이스를 적절히 붙인 경로로 두 번째 탐색을 수행합니다.
바이트코드 파일 쓰기
바이트코드 베이스가 None이 아니면 새 바이트코드 파일은 적절한 확장 디렉터리에 기록되며, sys.path에 있는 디렉터리에 직접 기록되는 일은 없습니다.
확장 디렉터리 정의하기
개념적으로 바이트코드 파일의 확장 디렉터리는 소스 파일이 있는 디렉터리 앞에 바이트코드 베이스를 붙인 디렉터리입니다. Unix 환경에서는 다음과 같습니다.:
pcb = os.path.abspath(sys.bytecodebase)
if sourcefile[0] == os.sep: sourcefile = sourcefile[1:]
augdir = os.path.join(pcb, os.path.dirname(sourcefile))
단일 루트 디렉터리 트리가 없는 Windows에서는 소스 파일이 있는 디렉터리의 드라이브 문자에서 끝의 콜론을 제거한 뒤 이를 디렉터리 구성 요소로 취급합니다. 따라서 확장 디렉터리는 다음과 같이 만들어집니다.
pcb = os.path.abspath(sys.bytecodebase)
drive, base = os.path.splitdrive(os.path.dirname(sourcefile))
drive = drive[:-1]
if base[0] == "\\": base = base[1:]
augdir = os.path.join(pcb, drive, base)
바이트코드 베이스의 위치 고정하기
프로그램이 시작될 때 PYTHONBYTECODEBASE 환경 변수의 값은 절대 경로로 바뀌고, 유효성 검사를 거친 뒤 sys 모듈에 추가됩니다. 사실상 다음과 같습니다.:
pcb = os.path.abspath(os.environ["PYTHONBYTECODEBASE"])
probe = os.path.join(pcb, "foo")
try:
open(probe, "w")
except IOError:
sys.bytecodebase = None
else:
os.unlink(probe)
sys.bytecodebase = pcb
이렇게 하면 사용자는 바이트코드 베이스를 상대 경로로 지정할 수 있으면서도, 프로그램 실행 중에 현재 작업 디렉터리가 바뀌어도 그 영향을 받지 않습니다. (프로그램 실행 중에 바이트코드 베이스가 이리저리 옮겨 다니기를 원하는 경우는 상상하기 어렵습니다.)
sys.bytecodebase에 특별한 점은 없습니다. 사용자는 원하는 경우 런타임에 이 값을 변경할 수 있지만, 보통은 수정되지 않습니다.
근거
많은 환경에서 root가 아닌 사용자는 Python 소스 파일이 있는 디렉터리에 쓸 수 없습니다. Python 소스는 일반적으로 설치할 때 바이트 컴파일되므로 대부분은 문제가 되지 않습니다. 그러나 바이트코드 파일이 없거나 갱신해야 하는 상황도 있습니다. 현재 사용자가 소스 파일이 있는 디렉터리에 쓸 수 없으면, 그 모듈을 임포트하는 프로그램을 실행할 때마다 성능 저하가 발생합니다. [3] 특정 상황에서는 경고 메시지가 생성될 수도 있습니다. 디렉터리에 쓸 수 있는 경우에는 서로 다른 두 프로세스가 거의 동시에 바이트코드 파일을 쓰려고 시도할 수 있으며, 그 결과 파일이 손상될 수 있습니다. [4]
RAM 디스크를 사용할 수 있는 환경에서는 성능상의 이유로 바이트코드 파일을 그러한 디스크의 디렉터리에 쓰는 것이 바람직할 수 있습니다. 마찬가지로 Python 소스 코드가 네트워크 파일 시스템에 있는 환경에서는 바이트코드 파일을 로컬 디스크에 캐시하는 것이 바람직할 수 있습니다.
대안
지금까지 제안된 유일한 다른 대안 [1]은 인터프리터에 -R 플래그를 추가하여 바이트코드 파일 쓰기를 아예 비활성화하는 것으로 보입니다. 이 제안은 그 대안을 포괄합니다. 명령줄 옵션을 추가하는 것도 물론 가능하지만, 설치 중에는 인터프리터의 명령줄을 쉽게 사용할 수 없으므로(프로그램 시작 초기에???) 아마 충분하지 않을 것입니다.
문제점
- 모듈의 __file__ 속성 해석. 저는 모듈의 __file__ 속성이 바이트코드 파일의 실제 위치를 반영해야 한다고 생각합니다. 모듈의 소스 코드 위치를 찾으려면 imp.find_module(module)을 사용해야 합니다.
- 보안 - root가 PYTHONBYTECODEBASE를 설정해 두었다면 어떻게 됩니까? 그렇습니다. 이는 보안 위험이 될 수 있지만, root 사용자가 하는 다른 많은 일도 마찬가지입니다. root 사용자는 설치 중인 경우를 제외하고는 PYTHONBYTECODEBASE를 설정하지 않는 것이 좋을 것입니다. 그래도 이 문제를 최소화할 수는 있을 것입니다. root로 실행될 때 인터프리터는 PYTHONBYTECODEBASE가 root 이외의 사용자도 쓸 수 있는 디렉터리를 가리키는지 검사해야 합니다. 그렇다면 예외나 경고를 발생시키고 sys.bytecodebase를 None으로 설정할 수 있습니다. 또는 다음 항목을 참고하십시오.
- 추가 보안 - PYTHONBYTECODEBASE가 일반 디렉터리(예를 들어 /tmp)를 가리키면 어떻게 됩니까? 이 경우 기존 바이트코드 파일은 그 파일의 소유자가 현재 사용자나 root일 때만 로드해야 할 것입니다. (Windows에서도 이것이 문제가 됩니까?)
- 이 PEP와 임포트 훅의 상호 작용은 아직 검토되지 않았습니다. 사실 이 아이디어를 구현하는 가장 좋은 방법은 임포트 훅일 수도 있습니다. PEP 302를 참고하십시오.
- 현재(PEP 304 이전) 환경에서는 소스 파일과 바이트코드 파일이 같은 디렉터리에 있으므로, 해당 바이트코드 파일이 생성된 후에 소스 파일을 삭제해도 안전합니다. 현재 정의된 PEP 304에서는 그렇지 않습니다. 확장 디렉터리에 있는 바이트코드 파일은 소스 파일이 있을 때만 고려되므로, “.pyc”로 끝나는 모듈 파일을 찾을 때는 전혀 고려되지 않습니다. 이 동작은 바뀌어야 할 수도 있다고 생각합니다.
예시
다음 예제에서 urllib 소스 코드는 /usr/lib/python2.3/urllib.py에 있고, /usr/lib/python2.3은 sys.path에 포함되어 있지만 현재 사용자가 쓸 수 없는 디렉터리입니다.
- 바이트코드 베이스는 /tmp입니다. /usr/lib/python2.3/urllib.pyc가 존재하며 유효합니다. urllib를 임포트하면 /usr/lib/python2.3/urllib.pyc의 내용이 사용됩니다. 확장 디렉터리는 참조하지 않습니다. 다른 바이트코드 파일은 생성되지 않습니다.
- 바이트코드 베이스는 /tmp입니다. /usr/lib/python2.3/urllib.pyc가 존재하지만 최신 상태가 아닙니다. urllib를 임포트하면, 생성된 바이트코드 파일은 값이 /tmp/usr/lib/python2.3인 확장 디렉터리의 urllib.pyc에 기록됩니다. 중간 디렉터리는 필요에 따라 생성됩니다.
- 바이트코드 베이스는 None입니다. urllib.pyc 파일을 찾을 수 없습니다. urllib을 임포트할 때 바이트코드 파일이 기록되지 않습니다.
- 바이트코드 베이스는 /tmp입니다. urllib.pyc 파일을 찾을 수 없습니다. urllib을 임포트할 때 생성된 바이트코드 파일은 값이 /tmp/usr/lib/python2.3인 확장 디렉터리에 기록됩니다. 필요에 따라 중간 디렉터리가 생성됩니다.
- 시작 시 PYTHONBYTECODEBASE는 존재하지 않는 /tmp/foobar입니다. 경고가 발생하고 sys.bytecodebase는 None으로 설정되며, 나중에 sys.bytecodebase가 유효하고 쓰기 가능한 디렉터리를 가리키도록 변경되지 않는 한 프로그램 실행 중에 바이트코드 파일이 기록되지 않습니다.
- 시작 시 PYTHONBYTECODEBASE는 /로 설정되어 있는데, 이 디렉터리는 존재하지만 현재 사용자가 쓸 수 없습니다. 경고가 발생하고 sys.bytecodebase는 None으로 설정되며, 나중에 sys.bytecodebase가 유효하고 쓰기 가능한 디렉터리를 가리키도록 변경되지 않는 한 프로그램 실행 중에 바이트코드 파일이 기록되지 않습니다. 특정 바이트코드 파일을 위해 구성된 확장 디렉터리가 현재 사용자에게 쓰기 가능하더라도, 중요한 것은 바이트코드 베이스 디렉터리 자체가 쓰기 가능한지 여부라는 점에 유의하십시오.
- 시작 시 PYTHONBYTECODEBASE는 빈 문자열로 설정되어 있습니다. sys.bytecodebase는 None으로 설정됩니다. 그러나 경고는 발생하지 않습니다. urllib을 임포트할 때 urllib.pyc 파일을 찾을 수 없으면 바이트코드 파일이 기록되지 않습니다.
이어지는 Windows 예제에서 urllib 소스 코드는 C:\PYTHON22\urllib.py에 있습니다. C:\PYTHON22는 sys.path에 포함되어 있지만 현재 사용자가 쓸 수 없습니다.
- 바이트코드 베이스는
C:\TEMP로 설정되어 있습니다.C:\PYTHON22\urllib.pyc가 존재하며 유효합니다. urllib을 임포트할 때C:\PYTHON22\urllib.pyc의 내용이 사용됩니다. 확장 디렉터리는 참조되지 않습니다. - 바이트코드 베이스는
C:\TEMP로 설정되어 있습니다.C:\PYTHON22\urllib.pyc가 존재하지만 오래되었습니다. urllib을 임포트할 때 새 바이트코드 파일은 값이C:\TEMP\C\PYTHON22인 확장 디렉터리에 기록됩니다. 필요에 따라 중간 디렉터리가 생성됩니다. - 시작 시 PYTHONBYTECODEBASE는
TEMP로 설정되어 있고, 애플리케이션 시작 시의 현재 작업 디렉터리는H:\NET입니다. 따라서 잠재적인 바이트코드 베이스는H:\NET\TEMP입니다. 이 디렉터리가 존재하고 현재 사용자가 쓸 수 있으면 sys.bytecodebase는 그 값으로 설정됩니다. 그렇지 않으면 경고가 발생하고 sys.bytecodebase는 None으로 설정됩니다. - 바이트코드 베이스는
C:\TEMP입니다. urllib.pyc 파일을 찾을 수 없습니다. urllib을 임포트할 때 생성된 바이트코드 파일은 값이C:\TEMP\C\PYTHON22인 확장 디렉터리에 기록됩니다. 필요에 따라 중간 디렉터리가 생성됩니다.
구현
Sourceforge에 있는 패치를 참조하십시오. [6]
참고 문헌
Copyright
This document has been placed in the public domain.