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 파일의 경우 생성될 때 사용된 최적화 수준, 즉 피홀러를 넘어서는 최적화가 없었다는 점이 명확히 구분되는 반면, 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”보다 우선하여 선택되었습니다. 최적화 수준의 선행 접두사가 “0”인 경우 혼동이 발생하지 않도록 “opt-” 접두사는 “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()는 바이트코드 파일 경로를 노출하고 importlib에서 직접 사용되기도 하는 API이므로 가장 중요한 변경이 필요합니다. 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()가 true여야 하고, 그렇지 않으면 ValueError가 발생합니다(이를 통해 파일 이름에 잘못된 문자가 사용되는 것을 방지합니다). optimization에 빈 문자열을 전달하면 최적화 정보의 추가가 억제되어 이 PEP 이전의 파일 이름 형식으로 되돌아갑니다.
Python 자체의 두 최적화 수준을 넘어서는 경우, 서드파티 코드는 최적화 이름의 해시를 사용하여 최적화 수준을 지정할 것으로 예상됩니다. 예를 들면 hashlib.sha256(','.join(['no dead code', 'const folding'])).hexdigest()와 같습니다. 이로 인해 파일 이름이 길어질 수 있지만, 대부분의 사용자는 __pycache__ 디렉터리의 내용을 확인하지 않는다고 가정하므로 문제가 되지 않습니다.
debug_override매개변수는 더 이상 사용되지 않도록 변경될 예정입니다. 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와 같은 것을 통한 실행 시점 최적화에서 상당한 성능 향상이 발생하며, 실행 전 바이트코드 최적화를 통해서는 그렇지 않다는 것입니다.
이 아이디어는 이 PEP에서 거부되었는데, 이는 CPython의 기존 최적화 수준이 유용하다고 여기는 사람들이 존재한다는 사실을 무시하기 때문입니다. 또한 이는 이 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]. 이 주제는 기술적인 문제가 아니라 개인적 선호의 문제이므로, 이 사안은 해결된 것으로 간주합니다.
바이트코드 메타데이터에 최적화 수준을 내장하기
바이트코드의 최적화 수준을 파일 이름에 포함시키는 대신 파일의 메타데이터에 포함시켜야 한다는 제안도 있었습니다. 이는 모든 인터프리터가 항상 단일한 바이트코드 사본을 가진다는 것을 의미합니다. 따라서 최적화 수준을 변경하려면 바이트코드를 다시 작성해야 하겠지만, 신경 써야 할 파일도 단 하나뿐이게 됩니다.
이 제안은 파이썬이 루트 수준 애플리케이션으로 설치되는 경우가 많아서 표준 라이브러리에 속한 모듈의 바이트코드 파일을 수정하는 것이 항상 가능하다는 사실 때문에 거부되었습니다. 이런 상황에서는 통합 관리자가 모든 사용자와 모든 상황에 대해 어떤 최적화 수준이 적절할지 추측해야 할 필요가 있게 됩니다. 여러 최적화 수준이 동시에 공존할 수 있게 허용함으로써, 통합 관리자는 사용자가 무엇을 원하는지 추측할 필요가 없어지고, 사용자는 자신이 원하는 최적화 수준을 활용할 수 있게 됩니다.
참고 자료
Copyright
This document has been placed in the public domain.