PEP 488 – PYO 파일 제거
- Author:
- Brett Cannon <brett at python.org>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 20-Feb-2015
- Python-Version:
- 3.5
- Post-History:
- 06-Mar-2015, 13-Mar-2015, 20-Mar-2015
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 Python에서 PYO 파일이라는 개념을 제거할 것을 제안합니다. 최적화 수준에 따라 바이트코드 파일을 구분하는 기능을 계속 지원하기 위해, 이 PEP는 최적화가 적용된 경우 바이트코드 저장소 디렉터리에서 PYC 파일 이름에 최적화 수준을 포함하도록 확장할 것을 제안합니다.
근거
현재 바이트코드 파일에는 PYC와 PYO라는 두 가지 종류가 있습니다. PYC 파일은 인터프리터 시작 시 최적화 수준이 지정되지 않은 경우(즉, -O가 지정되지 않은 경우) 생성되고 읽히는 바이트코드 파일입니다. PYO 파일은 어떤 최적화 수준이든 지정된 경우(즉, -O 또는 -OO가 지정된 경우) 읽고 쓰는 바이트코드 파일을 나타냅니다. 이는 PYC 파일은 생성될 때 사용된 최적화 수준, 즉 핍홀 최적화기(peepholer) 이외의 최적화는 없다는 점을 명확하게 나타내지만, PYO 파일은 그렇지 않다는 의미입니다. 이를 최적화 수준과 파일 확장자로 나타내면 다음과 같습니다.
- 0:
.pyc - 1 (
-O):.pyo - 2 (
-OO):.pyo
수준 1과 수준 2 최적화 모두에 .pyo 파일 확장자를 재사용하기 때문에, 바이트코드 파일을 생성하는 데 어떤 최적화 수준이 사용되었는지 명확하게 알 방법이 없습니다. PYO 파일을 읽는 측면에서 보면, 사용자가 모든 PYO 파일이 같은 최적화 수준으로 생성되었는지 주의 깊게 확인하지 않은 경우(보통은 모든 PYO 파일을 무작정 삭제한 다음 compileall 모듈을 사용하여 모든 PYO 파일을 새로 컴파일하는 방식으로 처리합니다 [1]), 인터프리터가 여러 최적화 수준이 뒤섞인 코드를 사용하게 될 수 있습니다. 이 문제는 astoptimizer 프로젝트 [2]를 사용하는 경우처럼, 인터프리터가 기본적으로 지원하는 수준을 넘어 Python 코드를 최적화할 때 더욱 심해집니다.
PYO 파일을 쓰는 측면에서 보면, 사용하려는 최적화 수준을 바꿀 때마다, 또는 마지막으로 PYO 파일을 생성할 때 어떤 최적화가 사용되었는지 확실하지 않을 때마다 모든 PYO 파일을 삭제해야 하므로 불필요한 파일 변경이 발생합니다. 또한 이 PEP에서 제안하는 변경을 통해 모든 최적화 수준의 바이트코드 파일을 미리 컴파일해 둘 수 있습니다. 이는 여러 최적화 수준에 .pyo 파일 확장자를 재사용하는 탓에 현재는 불가능한 일입니다.
바이트코드 전용 모듈의 배포에 관해서는, 코드 난독화와 더 작은 파일 배포라는 일반적인 사용 사례에서 .pyc 파일과 .pyo 파일을 모두 배포할 필요가 없습니다. 이는 바이트코드 전용 모듈이 최적화되지 않은 .pyc 파일 이름에서만 로드된다는 의미입니다.
제안
PYO 파일이 초래하는 모호함을 없애기 위해, 이 PEP는 PYO 파일이라는 개념과 이에 수반되는 .pyo 파일 확장자를 제거할 것을 제안합니다. 최적화 수준을 명확하게 하고 __pycache__ 디렉터리에서 최적화된 바이트코드 파일을 불필요하게 다시 생성하지 않도록, 바이트코드 파일을 생성하는 데 사용된 최적화 수준을 바이트코드 파일 이름에 포함합니다. 최적화 수준이 지정되지 않은 경우에는 이 PEP 이전의 .pyc 파일 이름을 사용합니다(즉, 파일 이름에 최적화 수준을 지정하지 않습니다). 예를 들어, CPython 3.5에서 foo.py라는 소스 파일은 인터프리터의 최적화 수준(없음, -O, -OO)에 따라 다음과 같은 바이트코드 파일을 가질 수 있습니다.
- 0:
foo.cpython-35.pyc(즉, 변경 없음) - 1:
foo.cpython-35.opt-1.pyc - 2:
foo.cpython-35.opt-2.pyc
현재 바이트코드 파일 이름은 PEP 3147 [3], [4]에서 정의한 다음 표현식을 대략적으로 사용하여 importlib.util.cache_from_source()가 생성합니다.:
'{name}.{cache_tag}.pyc'.format(name=module_name,
cache_tag=sys.implementation.cache_tag)
이 PEP는 최적화 수준이 지정된 경우 이 표현식을 다음과 같이 변경할 것을 제안합니다.:
'{name}.{cache_tag}.opt-{optimization}.pyc'.format(
name=module_name,
cache_tag=sys.implementation.cache_tag,
optimization=str(sys.flags.optimize))
“opt-” 접두사는 캐시 태그와 시각적으로 구분되는 구분자를 제공하기 위해 선택되었습니다. 최적화 수준을 캐시 태그 뒤에 둔 것은, 하나의 인터프리터에서는 달라지지 않는 모듈 이름과 캐시 태그를 기준으로 바이트코드 파일 이름의 사전순 정렬 순서를 유지하기 위해서입니다. “opt-” 접두사는 어느 정도 그 자체로 의미가 드러나도록 “o” 대신 선택되었습니다. “opt-” 접두사는 최적화 수준의 첫 글자가 “0”인 경우 혼동이 생기지 않도록 “O” 대신 선택되었습니다.
구분자로 하이픈 대신 마침표를 선택한 것은 최적화 수준이 캐시 태그로 지정되는 인터프리터 버전의 일부가 아님을 명확히 구분하기 위해서입니다. 또한 이는 파일 이름에서 의미상 서로 다른 개념을 마침표로 구분하는 방식과도 잘 맞습니다.
예를 들어, 인터프리터에 -OO가 전달되었다면 파일 이름은 importlib.cpython-35.pyo 대신 importlib.cpython-35.opt-2.pyc가 됩니다.
최적화 수준이 적용되지 않을 때 새 opt- 태그를 생략하면 하위 호환성이 높아질 것입니다. 이는 또한 최적화 수준을 쓸 일이 없는 Python 구현(예: PyPy [10])을 더 배려하는 방식이기도 합니다.
이 변경은 임포트 성능에 전혀 영향을 주지 않는다는 점에 유의해야 합니다. 임포트 시스템은 이미 인터프리터의 최적화 수준에 따라 하나의 바이트코드 파일만 찾고 그 파일이 없으면 새 바이트코드 파일을 생성하므로, __pycache__ 디렉터리에 바이트코드 파일이 더 많아질 수 있다고 해도 stat 호출 측면에서는 아무런 영향이 없습니다. 인터프리터는 앞으로도 최적화 수준에 따라 하나의 바이트코드 파일만 찾으므로 stat 호출이 늘어나지 않습니다.
이 PEP로 인해 생길 수 있는 유일한 부정적 결과는 .pyc 파일 수가 늘어나고 그에 따라 저장 공간 사용량도 늘어날 가능성이 높다는 점입니다. 하지만 이것이 문제가 되는 플랫폼을 위해 바이트코드 생성을 끌 수 있는 sys.dont_write_bytecode가 있으므로, 바이트코드 생성을 오프라인에서 관리할 수 있습니다.
구현
이 PEP의 구현을 사용할 수 있습니다 [11].
importlib
importlib.util.cache_from_source()는 바이트코드 파일 경로를 노출하는 API이면서 importlib에서 직접 사용되기도 하므로, 가장 중요한 변경이 필요합니다. Python 3.4 기준으로 이 함수의 시그니처는 다음과 같습니다.:
importlib.util.cache_from_source(path, debug_override=None)
이 PEP는 Python 3.5에서 시그니처를 다음과 같이 변경할 것을 제안합니다.:
importlib.util.cache_from_source(path, debug_override=None, *, optimization=None)
새로 도입되는 optimization 키워드 전용 매개변수는 파일 이름에 어떤 최적화 수준을 지정할지 제어합니다. 인자가 None이면 인터프리터의 현재 최적화 수준(최적화 없음 포함)을 사용하는 것으로 간주합니다. optimization에 주어진 인자는 모두 str()에 전달되며 str.isalnum()이 참이어야 하고, 그렇지 않으면 ValueError가 발생합니다(이는 파일 이름에 유효하지 않은 문자가 사용되는 것을 막습니다). optimization에 빈 문자열이 전달되면 최적화 수준을 추가하지 않으므로, 이 PEP 이전의 파일 이름 형식으로 되돌아갑니다.
Python 자체의 두 가지 최적화 수준 외에도, 서드파티 코드에서는 최적화 이름의 해시를 사용하여 최적화 수준을 지정할 것으로 예상됩니다. 예: hashlib.sha256(','.join(['no dead code', 'const folding'])).hexdigest(). 이로 인해 파일 이름이 길어질 수 있지만, 대부분의 사용자는 __pycache__ 디렉터리의 내용을 들여다보지 않는다고 가정하므로 문제가 되지 않을 것입니다.
debug_override 매개변수는 폐지 예정(deprecated)이 됩니다. False 값은 optimization=1과 같고, True 값은 optimization=''을 나타냅니다(None 인자는 계속해서 optimization에서와 같은 의미를 가집니다). debug_override에 None 이외의 값이 주어지면 폐지 예정 경고가 발생하지만, 현재로서는 이 매개변수를 완전히 제거할 계획이 없습니다(단, 제거하더라도 늦어도 Python 4에서는 제거됩니다).
바이트코드 파일 접미사와 관련된 importlib.machinery의 여러 모듈 속성이 갱신될 것입니다 [7]. DEBUG_BYTECODE_SUFFIXES와 OPTIMIZED_BYTECODE_SUFFIXES는 모두 폐지 예정으로 문서화되며 BYTECODE_SUFFIXES와 같은 값으로 설정됩니다(DEBUG_BYTECODE_SUFFIXES와 OPTIMIZED_BYTECODE_SUFFIXES의 제거는 현재 계획되어 있지 않지만, 제거하더라도 늦어도 Python 4에서는 제거됩니다).
여러 파인더와 로더도 필요에 따라 모두 갱신되지만, 앞에서 언급한 importlib의 부분들만 갱신하면 충분할 것입니다.
표준 라이브러리의 나머지 부분
py_compile과 compileall 함수가 노출하는 여러 함수는 새로운 바이트코드 파일 이름 의미 체계를 따르도록 필요에 따라 갱신됩니다 [6], [1]. compileall 모듈의 CLI는 직접적인 영향을 받지 않습니다(-O가 지정되어도 더 이상 .pyo 파일을 생성하지 않으므로 -b 플래그가 암묵적으로 적용됩니다).
호환성 고려 사항
Python 3.2 이후의 바이트코드 파일을 직접 조작하는 코드는 이 변경이 해당 코드에 미치는 영향을 고려해야 합니다(Python 3.2 이전에는 – Python 2 전체를 포함하여 – __pycache__가 없었으므로 이미 바이트코드 파일 처리 지원을 둘로 나누어야 했습니다). 코드에서 importlib.util.cache_from_source()에 debug_override 인자를 설정하고 있었다면, 최적화 수준이 2인 바이트코드 파일의 경로가 필요할 때 주의해야 합니다. 그 외에는 importlib.util.cache_from_source()를 사용하지 않는 코드만 갱신하면 됩니다.
바이트코드 전용 모듈을 배포하는(즉, 소스 파일 대신 바이트코드 파일을 사용하는) 사람들은 .pyc 파일과 함께 .pyo 파일을 배포하는 것이 더 이상 아무 쓸모가 없으므로, 바이트코드 파일의 최적화 수준을 무엇으로 할지 선택해야 합니다. 사람들은 보통 코드 난독화나 배포 크기 축소를 위해서만 바이트코드 파일을 배포하므로, 단일 .pyc만 배포하면 된다는 점은 오히려 이러한 사용 사례에 유리할 것입니다. 또한 PEP 465를 지원하기 위해 Python 3.5에서 바이트코드 파일의 매직 넘버가 변경되었으므로 기존 .pyo 파일을 지원할 필요가 없습니다 [8].
기각된 아이디어
CPython에서 최적화 수준을 완전히 제거하기
일부 사람들은 CPython에서 여러 최적화 수준을 수용하는 대신 이를 완전히 제거해야 한다고 제안했습니다. 그 인자는, 상당한 성능 향상은 실행 전 바이트코드 최적화가 아니라 JIT 같은 것을 통한 런타임 최적화에서 나온다는 것입니다.
이 아이디어는 CPython의 기존 최적화 수준을 실제로 유용하게 여기는 사람들이 있다는 사실을 무시하므로 이 PEP에서는 기각되었습니다. 또한 이 아이디어는 이 PEP가 제안하는 내용을 유용하게 여길 다른 Python 인터프리터가 없다고 가정합니다.
파일 이름에서 최적화 수준을 표기하는 다른 형식
“opt-” 접두사를 사용하고 최적화 수준을 캐시 태그와 파일 확장자 사이에 배치하는 것은 결정적인 사항이 아닙니다. 검토된 모든 선택지는 다음과 같습니다:
importlib.cpython-35.opt-1.pycimportlib.cpython-35.opt1.pycimportlib.cpython-35.o1.pycimportlib.cpython-35.O1.pycimportlib.cpython-35.1.pycimportlib.cpython-35-O1.pycimportlib.O1.cpython-35.pycimportlib.o1.cpython-35.pycimportlib.1.cpython-35.pyc
이 형식들은 바이트코드 파일의 정렬 순서를 바꾸거나, 캐시 태그와 혼동될 가능성이 있거나, 이름만으로 의미를 충분히 드러내지 못한다는 이유로 처음에 기각되었습니다. 비공식 투표를 진행한 결과, 사람들은 이 PEP가 제안한 형식을 분명히 선호했습니다 [9]. 이 주제는 기술적인 문제가 아니라 개인의 선호에 관한 것이므로 이 사안은 해결된 것으로 간주합니다.
바이트코드 메타데이터에 최적화 수준 포함하기
바이트코드의 최적화 수준을 파일 이름에 넣는 대신 파일의 메타데이터에 포함하자는 제안도 있었습니다. 이렇게 하면 모든 인터프리터는 어느 시점에서든 바이트코드 사본을 하나만 가지게 됩니다. 따라서 최적화 수준을 바꾸려면 바이트코드를 다시 작성해야 하지만, 신경 써야 할 파일은 하나뿐입니다.
이 제안은 Python이 흔히 루트 수준 애플리케이션으로 설치되므로 표준 라이브러리 모듈의 바이트코드 파일을 수정하는 것이 항상 가능하다는 사실 때문에 거부되었습니다. 이런 상황에서는 통합 작업자가 모든 상황에서 사용자에게 적절한 최적화 수준이 무엇인지 추측해야 합니다. 여러 최적화 수준이 동시에 공존할 수 있게 하면 통합 작업자는 사용자가 무엇을 원하는지 추측할 필요가 없어지고, 사용자는 자신이 원하는 최적화 수준을 활용할 수 있습니다.
참고 문헌
Copyright
This document has been placed in the public domain.