Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 552 – 결정론적 pyc

Author:
Benjamin Peterson <benjamin at python.org>
Status:
Final
Type:
Standards Track
Created:
04-Sep-2017
Python-Version:
3.7
Post-History:
07-Sep-2017
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 pyc 형식을 확장하여 더욱 결정론적으로 만들 것을 제안합니다.

근거

reproducible build은 동일한 소스를 빌드할 때마다, 심지어 서로 다른 시스템에서도(물론 서로 상당히 유사한 환경이 설정되어 있어야 한다는 조건하에) 바이트 단위로 동일한 출력이 생성되는 빌드입니다. 재현 가능성은 보안에 중요합니다. 또한 Bazel과 같은 콘텐츠 기반 빌드 시스템의 핵심 개념이기도 합니다. 이러한 시스템은 출력 파일의 콘텐츠가 입력 파일의 콘텐츠에 대한 결정론적 함수일 때 가장 효과적입니다.

현재 Python pyc 형식은 magic number와 소스 타임스탬프 및 소스 파일 크기가 앞에 붙은 모듈의 마셜된 코드 객체입니다. 소스 타임스탬프가 존재한다는 것은 pyc가 입력 파일 콘텐츠의 결정론적 함수가 아니라는 의미입니다. 즉, 변동 가능한 메타데이터인 소스의 mtime에도 의존합니다. 따라서 pyc는 적절한 재현 가능성을 가로막습니다.

Python 코드 배포자는 현재 다음 선택지에 묶여 있습니다.

  1. pyc를 배포하지 않아 캐싱의 이점을 잃기
  2. pyc를 배포하여 재현 가능성을 잃기
  3. 모든 Python 소스 파일에 신중하게 결정론적 타임스탬프 부여하기(예를 들어 https://github.com/python/cpython/pull/296 참조)
  4. 설치 시점에 pyc를 생성하는 것처럼 1번과 2번을 복잡하게 혼합하여 수행하기

이러한 선택지는 어느 것도 그다지 매력적이지 않습니다. 이 PEP는 타임스탬프를 결정론적 해시로 대체할 수 있도록 할 것을 제안합니다. 하지만 현재의 타임스탬프 무효화 방식은 기본값으로 유지됩니다. 비결정론적임에도 불구하고 타임스탬프 무효화는 많은 작업 흐름과 사용 사례에서 잘 작동합니다. 해시 기반 pyc 형식은 모든 소스 파일을 읽고 해시해야 하는 비용을 초래할 수 있으며, 이는 단순히 타임스탬프를 확인하는 것보다 비용이 많이 듭니다. 따라서 당분간은 주로 배포자와 고급 사용 사례에서 사용될 것으로 예상합니다.

(여기서 다루지 않는 다른 문제 [1][2] 로 인해 pyc가 비결정적이 될 수 있다는 점에 유의하십시오.)

사양

현재 pyc 헤더는 3개의 32비트 워드로 구성됩니다. 이를 4개로 확장합니다. 첫 번째 워드는 계속해서 매직 넘버로 사용되며, 바이트코드와 pyc 형식의 버전을 나타냅니다. 두 번째 워드, 즉 개념상 새로 추가된 워드는 비트 필드가 됩니다. 헤더 나머지 부분의 해석과 pyc의 무효화 동작은 비트 필드의 내용에 따라 달라집니다.

비트 필드가 0이면 pyc는 기존의 타임스탬프 기반 pyc입니다. 즉, 세 번째와 네 번째 워드는 각각 타임스탬프와 파일 크기가 되며, 소스 파일의 메타데이터를 헤더의 메타데이터와 비교하여 무효화합니다.

비트 필드의 최하위 비트가 설정되어 있으면 해당 pyc는 해시 기반 pyc입니다. 두 번째로 낮은 비트를 check_source플래그라고 부릅니다. 비트 필드 다음에는 소스 파일의 64비트 해시가 옵니다. 소스 파일의 내용을 하드코딩된 키로 사용하는 SipHash를 사용합니다. MD5나 BLAKE2와 같은 다른 빠른 해시도 사용할 수 있습니다. Python에는 이미 PEP 456에 따른 내장 SipHash 구현이 있기 때문에 SipHash를 선택하지만, SipHash 키를 선택할 수 있도록 하는 인터페이스는 Python에 노출되어야 합니다. 해시의 보안성은 중요하지 않지만, 통제된 환경에서 Python 감사를 용이하게 하기 위해 MD5와 같이 완전히 손상된 해시는 제외합니다.

Python이 해시 기반 pyc를 만났을 때의 동작은 check_source플래그 설정에 따라 달라집니다. check_source플래그가 설정되어 있으면 Python은 소스 파일을 해시하고 그 해시를 pyc에 저장된 예상 해시와 비교하여 pyc의 유효성을 판단합니다. pyc를 다시 생성해야 하는 경우 check_source플래그가 설정된 해시 기반 pyc로 다시 생성합니다.

check_source가 설정되지 않은 해시 기반 pyc의 경우 Python은 소스 파일의 해시를 확인하지 않고 pyc를 단순히 로드합니다. 이 경우에는 외부 시스템(예: 로컬 Linux 배포판의 패키지 관리자)이 pyc를 최신 상태로 유지할 책임이 있으므로 Python 자체는 확인할 필요가 없다는 것이 전제입니다. 유효성 검사가 비활성화된 경우에도 해시 필드는 올바르게 설정되어야 하며, 그래야 대역 외 일관성 검사기가 pyc의 최신 상태를 확인할 수 있습니다. 해당 소스 파일이 없는 pyc를 로드해서는 안 된다는 PEP 3147의 규정도 해시 기반 pyc에 대해서는 계속 적용됩니다.

py_compilecompileall의 프로그래밍 방식 API는 해시 기반 pyc 생성을 지원합니다. 특히 py_compile은 사용 가능한 모든 pyc 무효화 모듈에 대응하는 새로운 열거형을 정의합니다.:

class PycInvalidationMode(Enum):
    TIMESTAMP
    CHECKED_HASH
    UNCHECKED_HASH

py_compile.compile, compileall.compile_dir, compileall.compile_file 모두 invalidation_mode 매개변수를 갖게 되며, 이 매개변수는 PycInvalidationMode 열거형의 값을 받습니다.

compileall 도구에는 check_source 비트가 설정된 것과 설정되지 않은 해시 기반 pyc를 생성하기 위한 새로운 명령 옵션인 --invalidation-mode가 추가될 예정입니다. --invalidation-modePycInvalidationMode의 값에 대응하는 timestamp(기본값), checked-hash, unchecked-hash 값을 받는 3진 옵션이 될 것입니다.

importlib.util는 바이트열 source에 대해 pyc 작성 코드가 사용하는 해시를 계산하는 source_hash(source) 함수로 확장됩니다.

해시 기반 pyc 무효화의 런타임 구성은 새로운 --check-hash-based-pycs 인터프리터 옵션을 통해 용이해집니다. 이는 삼중 상태 옵션으로, default, always, never 세 가지 값을 받을 수 있습니다. 기본값인 default는 앞서 설명한 대로 해시 기반 pyc의 check_source 플래그가 무효화 여부를 결정함을 의미합니다. alwayscheck_source 비트의 값과 상관없이 인터프리터가 무효화를 위해 소스 파일을 해시하도록 합니다. never는 인터프리터가 항상 해시 기반 pyc를 유효하다고 간주하도록 합니다. --check-hash-based-pycs=never가 적용되어 있으면, 검사되지 않는 해시 기반 pyc는 검사되지 않는 해시 기반 pyc로 재생성됩니다. 타임스탬프 기반 pyc는 --check-hash-based-pycs의 영향을 받지 않습니다.

참고 문헌

감사의 말

저자는 Gregory P. Smith, Christian Heimes, Steve Dower에게 이 PEP의 주제에 관한 유익한 대화를 나눠 주셔서 감사드립니다.