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

Python 개선 제안 한국어 번역

PEP 432 – CPython 시작 시퀀스 재구성

Author:
Alyssa Coghlan <ncoghlan at gmail.com>, Victor Stinner <vstinner at python.org>, Eric Snow <ericsnowcurrently at gmail.com>
Discussions-To:
Capi-SIG list
Status:
Withdrawn
Type:
Standards Track
Requires:
587
Created:
28-Dec-2012
Post-History:
28-Dec-2012, 02-Jan-2013, 30-Mar-2019, 28-Jun-2020

Table of Contents

번역·라이선스 안내

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

PEP 철회

2012년 말부터 2020년 중반까지 이 PEP는 CPython 시작 시퀀스를 더 쉽게 유지 관리하고 CPython 런타임을 더 큰 애플리케이션의 일부로 더 쉽게 임베드하기 위한 일반적인 배경 지식과 구체적인 제안을 제공했습니다.

그 기간 대부분 동안 변경 사항은 별도의 기능 브랜치에서 유지 관리되거나, 메인 CPython 저장소에서 밑줄 접두사가 붙은 비공개 API로 유지 관리되었습니다.

2019년에 PEP 587은 이러한 API 변경 사항의 일부를 Python 3.8 이상을 위한 공개 CPython API로 이전했습니다(구체적으로 이 PEP는 인터프리터 런타임이 명시적인 다단계 구조체 기반 구성 인터페이스를 제공하도록 업데이트했습니다).

2020년 6월, 운영위원회의 문의에 따라 PEP 작성자들은 원래 PEP를 철회하는 것이 타당하다고 판단했습니다. PEP 432가 처음 작성된 이후 충분히 많은 변화가 있었으므로, 시작 시퀀스와 임베딩 API에 대한 추가 변경 사항은 새로운 PEP(또는 PEP들)로 구성하는 것이 가장 바람직하다고 판단했기 때문입니다. 새로운 PEP는 PEP 587에 반영될 만큼 충분히 검증되었다고 보기 어려워 아직 구현되지 않은 PEP 432의 아이디어뿐만 아니라, 공개 PEP 587API에 대한 피드백과 CPython 구현을 임베딩 및 서브인터프리터에 더 친화적으로 조정하는 과정에서 얻은 다른 모든 교훈도 고려해야 합니다.

특히 다음 변경 사항과 이를 가능하게 하는 데 필요한 추가 인프라 변경을 제안하는 PEP들은 여전히 검토할 가치가 있을 가능성이 높습니다.

  • 모든 사용자 수준 설정을 무시하고 기본적으로 격리 모드로 실행되는 대체 Python 실행 파일을 제공하여, 기본 인터프리터보다 시스템 수준 Python 애플리케이션 실행에 더 적합하게 만들기
  • 순수 Python 스크립트에서 단일 파일 실행 파일을 생성할 수 있도록 zipapp 모듈을 개선하기(다단계 확장 모듈 초기화가 도입된 점을 고려하면 Python 확장 모듈까지도 잠재적으로 지원하기)
  • 테스트 모음의 적용 범위와 해당 코드의 전반적인 유지 관리성을 개선하기 위해 복잡한 sys.path 초기화 로직을 C에서 Python으로 이전하기

초록

이 PEP는 CPython의 시작 시퀀스를 재구성하는 메커니즘을 제안합니다. 이를 통해 참조 인터프리터 실행 파일의 초기화 동작을 더 쉽게 수정할 수 있으며, 대체 실행 파일을 생성하거나 더 큰 애플리케이션 내부에 Python 실행 엔진으로 임베드할 때 CPython의 시작 동작을 더 쉽게 제어할 수 있습니다.

이 제안의 구현이 완료되면 인터프리터 시작은 명확히 구분되고 독립적으로 구성 가능한 다음 세 단계로 이루어집니다.

  • Python 핵심 런타임 사전 초기화
    • 메모리 관리 설정
    • 시스템 인터페이스에 사용되는 인코딩 결정(나중 구성 단계에 전달되는 설정 포함)
  • Python 핵심 런타임 초기화
    • C API를 사용할 수 있도록 준비
    • 내장 모듈과 동결된 모듈에 액세스할 수 있도록 보장
  • 주 인터프리터 구성
    • 외부 모듈에 액세스할 수 있도록 보장
    • (참고: 이 단계의 이름은 변경될 가능성이 매우 높습니다.)

주 모듈 실행과 서브인터프리터 초기화에 영향을 미치는 변경 사항도 제안됩니다.

참고: TBC = 확인 예정, TBD = 결정 예정입니다. 참조 구현이 개발됨에 따라 이러한 항목 대부분에 대한 적절한 해결 방안이 더 명확해질 것입니다.

제안

이 PEP는 CPython 런타임의 초기화를 명확히 구분되는 다음 세 단계로 나눌 것을 제안합니다.

  • 핵심 런타임 사전 초기화
  • 핵심 런타임 초기화
  • 주 인터프리터 구성

(이전 버전에서는 두 단계만 제안했지만, PEP를 CPython 내부 리팩터링으로 구현하려고 시도한 경험을 통해 문제를 명확히 분리하려면 최소 세 단계가 필요하다는 사실이 드러났습니다.)

제안된 설계는 다음 사항에도 중대한 영향을 미칩니다.

  • 메인 모듈 실행
  • 서브인터프리터 초기화

새로운 설계에서는 인터프리터가 초기화 시퀀스 동안 다음의 명확하게 정의된 단계를 거칩니다.

  • 초기화되지 않음 - 사전 초기화 단계조차 아직 시작하지 않음
  • 사전 초기화 - 사용 가능한 인터프리터 없음
  • 런타임 초기화됨 - 메인 인터프리터를 부분적으로 사용할 수 있지만, 서브인터프리터 생성은 아직 사용할 수 없음
  • 초기화됨 - 메인 인터프리터를 완전히 사용할 수 있으며, 서브인터프리터 생성을 사용할 수 있음

PEP 587은 사전 초기화 단계를 마지막 두 단계에서 분리하는 내용을 다루는 더 상세한 제안이지만, “런타임 초기화됨” 상태에서 임베딩 애플리케이션이 임의의 코드를 실행하도록 허용하지는 않습니다(대신 코어 런타임을 초기화하면 메인 인터프리터도 항상 완전히 초기화됩니다. Python 3.8에서도 네이티브 CPython CLI가 여전히 그런 방식으로 작동하기 때문입니다).

설계 변경을 이끄는 구체적인 사용 사례를 제공하고, 시스템 유틸리티에 적합한 기본값과 사용자 스크립트를 실행할 때의 기본값이 서로 다른 알려진 문제를 해결하기 위해, 이 PEP는 별도의 시스템 Python(system-python실행 파일)을 생성하고 배포할 것을 제안합니다. 이 실행 파일은 기본적으로 CPython의 -I스위치로 선택되는 “격리 모드”로 작동합니다. 또한 일반적인 CPython 시작 시퀀스를 거치는 대신 추가된 zip 아카이브를 실행하는 예제 스텁 바이너리도 생성할 것을 제안합니다(단일 파일 순수 Python 실행 파일을 사용할 수 있게 합니다).

구현 복잡성을 통제하기 위해, 이 PEP는 런타임에 인터프리터 상태에 접근하는 방식을 전면적으로 변경하는 것을 제안하지 않습니다. 시작 시퀀스를 더 쉽게 유지 관리하기 위해 기존 초기화 단계가 발생하는 순서를 변경하는 것만으로도 이미 상당한 변경이며, 동시에 다른 변경까지 시도하면 변경 범위가 훨씬 더 커지고 검토하기도 훨씬 어려워집니다. 그러나 그러한 제안은 후속 PEP나 패치에 적합한 주제가 될 수 있습니다. - 이 PEP와 관련 하위 제안의 주요 이점 중 하나는 내부 저장 모델과 구성 인터페이스 사이의 결합을 줄이는 것입니다. 따라서 이 PEP가 구현되고 나면 그러한 변경을 더 쉽게 수행할 수 있을 것입니다.

배경

시간이 지나면서 CPython의 초기화 시퀀스는 점점 더 복잡해졌으며, 더 많은 옵션을 제공하고 더 복잡한 작업도 수행하게 되었습니다(예를 들어 Python 3에서 OS 인터페이스의 유니코드 설정 구성 [10], 순수 Python으로 작성된 import 시스템 구현의 부트스트랩, 높은 권한으로 실행되는 시스템 애플리케이션에 더 적합한 격리 모드 구현 [6] 등이 있습니다).

이러한 복잡성의 대부분은 공식적으로 Py_MainPy_Initialize API를 통해서만 접근할 수 있으므로, 임베딩 애플리케이션에서 사용자 지정할 기회가 거의 없습니다. 이러한 점진적으로 증가하는 복잡성은 유지 관리자들의 작업도 어렵게 만듭니다. 구성의 상당 부분이 Py_Initialize 호출 전에 이루어져야 하므로 Python C API의 상당 부분을 안전하게 사용할 수 없기 때문입니다.

더욱 정교한 시작 동작을 위한 여러 제안이 제시되어 있으며, 여기에는 sys.path 초기화를 더욱 잘 제어하는 방법(예를 들어 플랫폼 간 방식으로 명령줄에서 추가 디렉터리를 쉽게 추가하는 방법 [7], sys.path[0]의 구성을 제어하는 방법 [8]), Python 하위 프로세스를 시작할 때 적용 범위 추적과 같은 유틸리티를 더 쉽게 구성하는 방법 [9]) 등이 포함됩니다.

이미 복잡한 시스템에 이러한 동작을 무기한 덧붙이는 대신, 이 PEP는 더 구조화된 시작 시퀀스를 도입하여 현재 상태를 단순화하기 시작할 것을 제안합니다. 이는 이러한 추가 기능 요청을 더 쉽게 구현하는 것을 목표로 합니다.

원래 전체 제안은 이 하나의 PEP에서 관리했지만, 이것이 비현실적인 것으로 드러났습니다. 따라서 제안된 설계의 일부가 안정화됨에 따라 이를 각각의 PEP로 분리하여, 전체 설계의 세부 사항이 여전히 발전하는 동안에도 진전을 이룰 수 있도록 했습니다.

주요 고려 사항

시작 시퀀스를 변경할 때 고려해야 할 몇 가지 주요 사항이 있습니다.

유지 관리성

Python 3.6 당시의 CPython 시작 시퀀스는 이해하기 어려웠으며, 수정하기는 더욱 어려웠습니다. 초기화 코드의 상당 부분이 실행되는 동안 인터프리터가 어떤 상태에 있었는지가 명확하지 않았습니다. 이로 인해 -X 또는 -W 옵션을 사용할 때 Py_Initialize 호출 전에 리스트, 딕셔너리 및 유니코드 값이 생성되는 것과 같은 동작이 발생했습니다 [1].

명시적인 다단계 시작 시퀀스로 전환하면 개발자는 다음 사항만 이해하면 됩니다.

  • 사전 구성 이전에 사용할 수 있는 API와 기능(사실상 사전 구성 API 자체를 제외하면 없음)
  • 코어 런타임 구성 이전에 사용할 수 있는 API와 기능. 사전 구성이 명시적으로 실행되지 않았다면 Python 3.6의 동작과 일치하는 기본 설정으로 사전 구성을 암묵적으로 실행합니다.
  • 메인 인터프리터가 완전히 구성된 후에만 사용할 수 있는 API와 기능(이는 전체 C API의 비교적 작은 부분 집합이 되기를 바랍니다)

이 중 처음 두 가지 측면은 PEP 587에서 다루고 있으며, 마지막 구분의 세부 사항은 아직 검토 중입니다.

C 구조체와 Python 데이터 형식의 조합을 기반으로 새로운 설계를 구성하면, 앞으로 시스템을 수정하여 새로운 구성 옵션을 추가하기도 더 쉬워질 것입니다.

테스트 가능성

CPython 시작 시퀀스가 복잡해지면서 발생하는 문제 중 하나는 서로 다른 구성 설정 간 가능한 상호 작용이 조합적으로 폭발한다는 점입니다.

이 문제는 새로운 초기화 시스템의 설계와 이를 구현하기 위해 제안된 접근 방식 모두에 영향을 줍니다.

성능

CPython은 런타임이 인터프리터 초기화 시간에 크게 좌우되는 짧은 스크립트를 실행하는 데 많이 사용됩니다. 시작 시퀀스에 대한 변경은 시작 오버헤드에 미치는 영향을 최소화해야 합니다.

importlib 마이그레이션 경험에 따르면 시작 시간은 IO 작업에 의해 크게 좌우됩니다. 그러나 변경 사항의 영향을 모니터링하기 위해 간단한 벤치마크를 사용하여 인터프리터를 시작한 다음 종료하는 데 얼마나 걸리는지 확인할 수 있습니다.

python3 -m timeit -s "from subprocess import call" "call(['./python', '-Sc', 'pass'])"

Fedora 프로젝트에서 빌드한 Python 3.7에 대해 내 시스템에서 측정한 현재 수치는 다음과 같습니다.

$ python3 -m timeit -s "from subprocess import call" "call(['python3', '-Sc', 'pass'])"
50 loops, best of 5: 6.48 msec per loop

(TODO: 표준 라이브러리 timeit 대신 perf로 이 마이크로벤치마크 실행)

이 PEP는 시작 시간에 큰 영향을 미치지 않을 것으로 예상됩니다. 개별 단계에 실질적인 변경을 가하지 않고 기존 초기화 시퀀스를 주로 재배열하는 것을 목표로 하기 때문입니다.

그러나 이 간단한 확인에서 제안된 초기화 시퀀스 변경이 성능 문제를 일으킬 수 있다고 판단되면 조사를 지원하기 위해 더 정교한 마이크로벤치마크를 개발합니다.

필수 구성 설정

CPython 인터프리터 구성 설정과 설정에 사용할 수 있는 다양한 방법은 자세히 나열한 PEP 587을 참조하십시오.

구현 전략

Python 3.4용 이 PEP의 이전 버전을 구현하려는 초기 시도가 있었으며 [2], 이때 발생한 중요한 문제 중 하나는 리팩터링 프로세스를 시작하기 위해 초기 구조 변경을 적용한 후 병합 충돌이 발생했다는 점입니다. Python 2.5에서 AST 기반 컴파일러로 전환한 것이나 Python 3.3에서 import 시스템을 importlib 구현으로 전환한 것과 같은 다른 과거의 주요 변경과 달리, 원래 시도를 괴롭혔던 종류의 병합 충돌이 발생하기 쉬운 초안 구현을 피할 수 있도록 구성하는 명확한 방법은 없습니다.

따라서 구현 전략을 수정하여 먼저 이 리팩터링을 CPython 3.7의 비공개 API로 구현한 다음, 새로운 함수와 구조를 CPython 3.8의 공개 API 요소로 공개하는 것이 가능한지 검토하기로 했습니다.

초기 병합 후 Victor Stinner은 PEP 540 UTF-8 모드 변경을 성공적으로 구현하기 위해 설정을 실제로 새로운 구조로 마이그레이션하기 시작했습니다. 이 변경에는 이전에 로캘 인코딩으로 디코딩된 모든 설정을 추적한 다음 UTF-8을 사용하여 다시 디코딩할 수 있어야 했습니다. Eric Snow도 서브인터프리터 기능을 더욱 견고하게 만드는 과정의 일환으로 여러 내부 서브시스템을 마이그레이션했습니다.

이 작업을 통해 이 PEP에서 처음 제안한 세부 설계에 여러 실무적 문제가 있음이 드러났습니다. 따라서 Victor는 개선된 비공개 API를 설계하고 구현했으며, PEP 587은 이를 Python 3.8에서 공개 API로 승격할 것을 제안합니다.

설계 세부 사항

Note

여기의 API 세부 사항은 아직도 상당히 유동적입니다. 비공개 API의 현재 상태를 보여 주는 헤더 파일은 주로 다음과 같습니다.

PEP 587은 공개하기에 충분히 안정적일 가능성이 있다고 판단되는 API의 측면을 다룹니다. 제안된 API가 해당 PEP에서 다루어지는 경우 아래 텍스트에 “(PEP 587 참조)”를 추가합니다.

이 제안의 주요 내용은 시작 프로세스에서 핵심 언어 런타임을 초기화하고 주 인터프리터를 위한 부분적으로 초기화된 인터프리터 상태를 훨씬 더 이른 시점에 생성하는 것입니다. 이를 통해 초기화 프로세스의 나머지 단계에서 대부분의 CPython API를 사용할 수 있게 되며, 현재 CPython C API가 제공하는 더 풍부한 데이터 구조를 사용하지 못하고 기본적인 C 기능에 의존해야 하는 여러 작업을 잠재적으로 단순화할 수 있습니다.

PEP 587은 이 작업의 일부를 다룹니다. 즉, 기존의 “Py_Initialize 호출 전에 호출할 수 있음” 인터페이스에도 필요한 구성 요소(메모리 할당자와 운영 체제 인터페이스 인코딩 세부 사항 등)를 별도의 사전 구성 단계로 분리합니다.

이하에서 “임베딩 애플리케이션”이라는 용어는 표준 CPython 명령줄 애플리케이션도 포함합니다.

인터프리터 초기화 단계

다음과 같이 서로 구별되는 인터프리터 초기화 단계를 제안합니다.

  • 초기화되지 않음:
    • 실제 단계라기보다는 단계가 없는 상태입니다.
    • Py_IsInitializing()0을 반환합니다.
    • Py_IsRuntimeInitialized()0을 반환합니다.
    • Py_IsInitialized()0을 반환합니다.
    • 임베딩 애플리케이션은 사용할 메모리 할당자와 운영 체제 인터페이스에 액세스할 때 사용할 인코딩을 결정하거나, 이러한 결정을 Python 런타임에 위임합니다.
    • 애플리케이션은 Py_PreInitialize API 중 하나를 호출하여 초기화 프로세스를 시작합니다(PEP 587 참조).
  • 런타임 사전 초기화:
    • 인터프리터를 사용할 수 없습니다.
    • Py_IsInitializing()1을 반환합니다.
    • Py_IsRuntimeInitialized()0을 반환합니다.
    • Py_IsInitialized()0을 반환합니다.
    • 임베딩 애플리케이션은 핵심 CPython 런타임을 초기화하고 주 인터프리터를 생성하는 데 필요한 설정을 결정한 후 적용하며, Py_InitializeRuntime을 호출하여 다음 단계로 이동합니다.
    • 참고: PEP 587부터는 임베딩 애플리케이션이 대신 Py_Main(), Py_UnixMain 또는 Py_Initialize API 중 하나를 호출하므로 초기화됨 상태로 바로 이동합니다.
  • 주 인터프리터 초기화:
    • 내장 데이터 형식과 기타 핵심 런타임 서비스를 사용할 수 있습니다.
    • 주 인터프리터를 사용할 수 있지만, 부분적으로만 구성된 상태입니다.
    • Py_IsInitializing()1을 반환합니다.
    • Py_IsRuntimeInitialized()1을 반환합니다.
    • Py_IsInitialized()0을 반환합니다.
    • 임베딩 애플리케이션은 Py_InitializeMainInterpreter를 호출하여 초기화 프로세스를 완료하는 데 필요한 설정을 결정하고 적용합니다.
    • 참고: PEP 587부터는 이 상태에 공개 API를 통해 도달할 수 없으며, Py_Initialize 함수 중 하나가 실행되는 동안 암묵적으로 존재하는 내부 상태일 뿐입니다.
  • 초기화됨:
    • 주 인터프리터를 사용할 수 있고 완전히 작동하지만, __main__ 관련 메타데이터는 불완전합니다.
    • Py_IsInitializing()0을 반환합니다.
    • Py_IsRuntimeInitialized()1을 반환합니다.
    • Py_IsInitialized()1을 반환합니다.

단계 호출

나열된 모든 단계는 표준 CPython 인터프리터와 제안된 System Python 인터프리터에서 사용됩니다.

기존 Py_Initialize 또는 Py_Main() API를 사용하면 임베딩 애플리케이션은 초기화를 거의 전적으로 CPython의 제어하에 계속 둘 수 있으며, 하위 호환성이 보존됩니다.

또는 임베딩 애플리케이션이 CPython의 초기 상태를 더 세밀하게 제어하려는 경우, 초기화 프로세스를 더 폭넓게 제어할 수 있도록 하는 새로운 세분화된 API를 사용할 수 있습니다.

PEP 587은 코어 런타임 초기화를 주 인터프리터 초기화에서 분리하려고 시도하지 않고 사전 초기화 단계를 분리하는 해당 API의 초기 버전을 다룹니다.

초기화되지 않은 상태

초기화되지 않은 상태에서는 임베딩 애플리케이션이 임베드된 Python 런타임에 구성 설정을 올바르게 전달하는 데 필요한 설정을 결정합니다.

여기에는 Python에 사용할 메모리 할당자와 제공된 설정을 처리할 때 사용할 텍스트 인코딩을 알리는 작업이 포함됩니다.

PEP 587PyPreConfig 구조체에서 이 상태를 벗어나는 데 필요한 설정을 정의합니다.

새로운 쿼리 API를 사용하면 코드가 인터프리터가 초기화 프로세스를 시작하기조차 했는지 확인할 수 있습니다.:

int Py_IsInitializing();

완전히 초기화되지 않은 환경에 대한 쿼리는 !(Py_Initialized() || Py_Initializing())가 됩니다.

런타임 사전 초기화 단계

Note

관련 PEP 587에서는 이 단계의 설정이 아직 분리되어 있지 않으며, 대신 결합된 PyConfig 구조체를 통해서만 사용할 수 있습니다.

사전 초기화 단계에서는 CPython 런타임을 초기화하기 전에 반드시 필요한 설정을 임베딩 애플리케이션이 결정합니다. 현재 이 범주의 주요 구성 설정은 무작위화된 해시 알고리즘과 관련된 설정입니다. 해시 알고리즘은 프로세스의 수명 동안 일관되어야 하므로 코어 인터프리터가 생성되기 전에 설정되어 있어야 합니다.

필요한 필수 설정은 무작위화된 해시에 특정 시드 값을 사용할지 여부를 나타내는 플래그와, 사용하는 경우 해당 시드의 구체적인 값입니다. 시드 값이 0이면 무작위화된 해시가 비활성화됩니다. 또한 해시 무작위화를 구성할 때 PYTHONHASHSEED를 사용할 수 있으므로 환경 변수를 고려할지 여부도 초기에 결정해야 합니다. 마지막으로 CPython 빌드 프로세스를 지원하기 위해 가져오기 시스템을 완전히 비활성화하는 옵션이 제공됩니다.

시작 시퀀스의 이 단계에 대해 제안된 API는 다음과 같습니다.:

PyInitError Py_InitializeRuntime(
    const PyRuntimeConfig *config
);

PyInitError Py_InitializeRuntimeFromArgs(
    const PyRuntimeConfig *config, int argc, char **argv
);

PyInitError Py_InitializeRuntimeFromWideArgs(
    const PyRuntimeConfig *config, int argc, wchar_t **argv
);

Py_IsInitializing()이 거짓이면 Py_InitializeRuntime 함수가 해당하는 Py_PreInitialize 함수를 암시적으로 호출합니다. use_environment 설정은 전달되며, 다른 설정은 PEP 587에 설명된 대로 기본값에 따라 처리됩니다.

PyInitError 반환 형식은 PEP 587에 정의되어 있으며, 임베딩 애플리케이션이 Python 런타임 초기화 실패를 정상적으로 처리할 수 있도록 합니다. 따라서 전체 프로세스가 Py_FatalError에 의해 갑자기 종료되지 않습니다.

새로운 PyRuntimeConfig 구조체는 코어 런타임의 예비 구성과 주 인터프리터 생성에 필요한 설정을 보유합니다.:

/* Note: if changing anything in PyRuntimeConfig, also update
 * PyRuntimeConfig_INIT */
typedef struct {
    bool use_environment;     /* as in PyPreConfig, PyConfig from PEP 587 */
    int use_hash_seed;        /* PYTHONHASHSEED, as in PyConfig from PEP 587 */
    unsigned long hash_seed;  /* PYTHONHASHSEED, as in PyConfig from PEP 587 */
    bool _install_importlib;  /* Needed by freeze_importlib */
} PyRuntimeConfig;

/* Rely on the "designated initializer" feature of C99 */
#define PyRuntimeConfig_INIT {.use_hash_seed=-1}

코어 구성 설정 포인터는 NULL일 수 있으며, 이 경우 기본값은 PyRuntimeConfig_INIT에 지정된 값입니다.

PyRuntimeConfig_INIT매크로는 합리적인 기본값으로 구조체 인스턴스를 쉽게 초기화할 수 있도록 설계되었습니다.:

PyRuntimeConfig runtime_config = PyRuntimeConfig_INIT;

use_environment는 Python 관련 모든 환경 변수의 처리를 제어합니다. 플래그가 참이면 PYTHONHASHSEED는 정상적으로 처리됩니다. 그렇지 않으면 모든 Python 전용 환경 변수가 정의되지 않은 것으로 간주됩니다. 단, 앱 번들과 기본 Python 바이너리 간 통신에 사용되는 Mac OS X의 환경 변수처럼 일부 운영 체제별 환경 변수에는 예외가 적용될 수 있습니다.

use_hash_seed는 무작위화된 해시 알고리즘의 구성을 제어합니다. 값이 0이면 임의의 시드를 사용하는 무작위화된 해시가 사용됩니다. 양수이면 hash_seed의 값이 난수 제너레이터의 시드로 사용됩니다. 이 경우 hash_seed가 0이면 무작위화된 해시가 완전히 비활성화됩니다.

use_hash_seed이 음수이고 use_environment이 true이면 CPython은 PYTHONHASHSEED 환경 변수를 검사합니다. 환경 변수가 설정되지 않았거나, 빈 문자열 또는 "random"이라는 값으로 설정된 경우에는 무작위 시드를 사용하는 무작위화된 해시가 사용됩니다. 환경 변수가 문자열 "0"으로 설정된 경우에는 무작위화된 해시가 비활성화됩니다. 그 밖의 경우 해시 시드는 [0; 4294967295] 범위에 있는 정수의 문자열 표현이어야 합니다.

임베딩 애플리케이션에서 다른 데이터 소스를 사용하여 PYTHONHASHSEED 처리를 쉽게 사용할 수 있도록 다음 헬퍼 함수가 C API에 추가됩니다.:

int Py_ReadHashSeed(char *seed_text,
                    int *use_hash_seed,
                    unsigned long *hash_seed);

이 함수는 seed_text에서 시드 문자열을 받아 적절한 플래그와 시드 값으로 변환합니다. seed_textNULL이거나 빈 문자열 또는 "random"이라는 값이면 use_hash_seedhash_seed가 모두 0으로 설정됩니다. 그렇지 않으면 use_hash_seed1로 설정되고 시드 텍스트는 정수로 해석되어 hash_seed로 보고됩니다. 성공하면 함수는 0을 반환합니다. 0이 아닌 반환 값은 오류가 발생했음을 나타냅니다(대부분 정수로 변환하는 과정에서 발생합니다).

_install_importlib 설정은 가져오기 기능이 전혀 없는 인터프리터를 생성하기 위한 CPython 빌드 프로세스의 일부로 사용됩니다. 현재 지원되는 유일한 사용 사례가 importlib._bootstrap의 이전에 동결된 바이트코드를 무효화하는 컴파일러 변경을 빌드 프로세스를 중단하지 않고 허용하는 것이므로, 이 설정은 CPython 개발 팀의 사적인 설정으로 간주됩니다(따라서 앞에 밑줄이 붙습니다).

서로 다른 임베딩 애플리케이션에서 부트스트래핑 환경을 일관되게 유지하려면 이 초기 구성 수준을 가능한 한 작게 유지하는 것이 목표입니다. 이 설정 없이 유효한 인터프리터 상태를 생성할 수 있다면, 이 설정은 핵심 런타임 구성 대신 포괄적인 PyConfig 구조체에만 나타나야 합니다.

새로운 쿼리 API를 사용하면 코드에서 핵심 런타임 초기화 이후부터 주 인터프리터 상태 생성 및 주 인터프리터 초기화 과정의 대부분이 완료될 때까지 인터프리터가 부트스트래핑 상태인지 확인할 수 있습니다.:

int Py_IsRuntimeInitialized();

Py_IsRuntimeInitialized()가 이미 true인 경우 Py_InitializeRuntime()를 다시 호출하려고 하면 사용자 구성 오류로 보고됩니다. (추후 결정 사항: 기존 공개 초기화 API는 오류 없이 여러 번 호출할 수 있고, 한 번만 쓸 수 있는 설정에 대한 변경을 단순히 무시하기 때문입니다. 새로운 API를 기존 API보다 더 엄격하게 만들기보다는 이러한 동작을 유지하는 것이 타당할 수 있습니다)

이제 아직 완전히 초기화되지 않은 인터프리터에서도 동결된 바이트코드를 합법적으로 실행할 수 있으므로 sys.flags에 새로운 initialized 플래그가 추가됩니다.

핵심 런타임이 초기화되면 주 인터프리터와 대부분의 CPython C API는 완전히 작동해야 하지만 다음은 예외입니다:

  • 컴파일은 허용되지 않습니다(파서와 컴파일러가 아직 적절히 구성되지 않았기 때문입니다).
  • 서브인터프리터 생성은 허용되지 않습니다.
  • 추가 스레드 상태 생성은 허용되지 않습니다.
  • sys 모듈의 다음 속성은 모두 없거나 None입니다: * sys.path * sys.argv * sys.executable * sys.base_exec_prefix * sys.base_prefix * sys.exec_prefix * sys.prefix * sys.warnoptions * sys.dont_write_bytecode * sys.stdin * sys.stdout
  • 파일 시스템 인코딩은 아직 정의되지 않았습니다.
  • IO 인코딩은 아직 정의되지 않았습니다.
  • CPython 시그널 처리기는 아직 설치되지 않았습니다.
  • 위 제한으로 인해 내장 모듈과 프리즈된 모듈만 가져올 수 있습니다.
  • sys.stderr는 버퍼링되지 않는 바이너리 모드를 사용하는 임시 IO 객체로 설정됩니다.
  • sys.flags속성은 존재하지만 개별 플래그의 최종 값은 아직 정해지지 않았을 수 있습니다.
  • sys.flags.initialized속성은 0으로 설정됩니다.
  • warnings 모듈은 아직 초기화되지 않았습니다.
  • __main__ 모듈은 아직 존재하지 않습니다.

<TBD: 식별해야 할 다른 주목할 만한 누락 기능>

이 단계에서 사용할 수 있게 되는 주요 항목은 핵심 Python 데이터 형식이며, 특히 딕셔너리, 리스트 및 문자열입니다. 이를 통해 현재 상태와 달리, 이후의 모든 구성 단계에서 이러한 항목을 안전하게 사용할 수 있습니다.

또한 현재 스레드는 유효한 Python 스레드 상태를 보유하게 되므로, 이후의 구성 데이터를 C 프로세스 전역 변수 대신 주 인터프리터 객체에 저장할 수 있습니다.

Py_InitializeRuntime()를 호출할 때마다 이에 대응하는 Py_Finalize()호출이 있어야 합니다. 그사이에 Py_InitializeMainInterpreter()호출을 생략해도 됩니다(예를 들어 주 인터프리터 구성 설정을 빌드하려는 시도가 실패한 경우).

나머지 구성 설정 결정

초기화 시퀀스의 다음 단계는 프로세스를 완료하는 데 필요한 나머지 설정을 결정하는 것입니다. 이 시점에는 인터프리터 상태가 변경되지 않습니다. 이 단계의 핵심 API는 다음과 같습니다.:

int Py_BuildPythonConfig(
    PyConfigAsObjects *py_config, const PyConfig *c_config
);

int Py_BuildPythonConfigFromArgs(
    PyConfigAsObjects *py_config, const PyConfig *c_config, int argc, char **argv
);

int Py_BuildPythonConfigFromWideArgs(
    PyConfigAsObjects *py_config, const PyConfig *c_config, int argc, wchar_t **argv
);

py_config 인자는 PyConfigAsObjects 구조체를 가리키는 포인터여야 합니다(C 스택에 저장된 임시 구조체일 수도 있습니다). 이미 구성된 값(즉, NULL이 아닌 포인터)의 경우 CPython은 제공된 값의 타당성을 검사하지만, 그 외에는 해당 값을 올바른 것으로 받아들입니다.

구조체는 Python 딕셔너리 대신 사용됩니다. 구조체가 C에서 다루기 더 쉽고, 지원되는 필드 목록이 특정 CPython 버전에 대해 고정되어 있으며, Python 코드에는 읽기 전용 뷰만 노출하면 되기 때문입니다(sys.implementation을 노출하기 위해 이미 마련된 인프라 덕분에 이는 비교적 간단합니다).

Py_InitializeRuntime와 달리, 구성 데이터에 문제가 발견되면 이 호출은 Python 예외를 발생시키고 오류 반환을 보고하며 Python 초기화에 특화된 C 구조체를 반환하지 않습니다.

지원되는 구성 설정 중 아직 설정되지 않은 항목은 제공된 구성 구조체에 적절한 값으로 채워집니다. Py_BuildPythonConfig를 호출하기 전에 값을 설정하여 기본 구성을 완전히 재정의할 수 있습니다. 그러면 제공된 값은 해당 값에서 파생되는 다른 설정을 계산할 때도 사용됩니다.

또는 Py_BuildPythonConfig를 호출한 에도 설정을 재정의할 수 있습니다(이는 임베딩 애플리케이션이 설정을 완전히 대체하기보다 조정하려는 경우, 예를 들어 sys.path[0]을 제거하려는 경우에 유용할 수 있습니다).

c_config 인자는 PEP 587에 정의된 PyConfig 구조체를 가리키는 선택적 포인터입니다. 제공된 경우에는 환경 또는 프로세스 전역 상태에서 설정을 직접 읽는 것보다 우선하여 사용됩니다.

구성을 읽기만 해서는 인터프리터 상태에 아무런 영향도 주지 않습니다. 전달된 구성 구조체만 수정합니다. 설정은 아래에 설명된 Py_InitializeMainInterpreter 호출이 이루어질 때까지 실행 중인 인터프리터에 적용되지 않습니다.

지원되는 구성 설정

인터프리터 구성은 두 부분으로 나뉩니다. 하나는 주 인터프리터에만 관련되거나 주 인터프리터와 모든 서브인터프리터에서 동일해야 하는 설정이고, 다른 하나는 서브인터프리터마다 달라질 수 있는 설정입니다.

참고: 초기 구현에서는 인터프리터가 주 인터프리터인지 여부를 나타내는 플래그만 인터프리터별로 구성합니다. 다른 필드들은 구현 과정에서 인터프리터별로 만드는 것이 실현 가능한지 여부를 검토할 것입니다.

Note

아래의 설정 필드 목록은 현재 PEP 587과 동기화되어 있지 않습니다. 차이가 있는 경우, PEP 587이 우선합니다.

PyConfigAsObjects구조체는 PEP 587PyConfig구조체를 그대로 반영하지만, C 수준 데이터 형식 대신 완전한 Python 객체를 사용하여 값을 저장합니다. 나중의 초기화 단계에서 해당 필드를 별도로 전달하지 않아도 되도록 raw_argvargv 목록 필드를 추가합니다.

필드는 항상 Python 데이터 형식에 대한 포인터이며, 설정되지 않은 값은 NULL로 표시됩니다.:

typedef struct {
    /* Argument processing */
    PyListObject *raw_argv;
    PyListObject *argv;
    PyListObject *warnoptions; /* -W switch, PYTHONWARNINGS */
    PyDictObject *xoptions;    /* -X switch */

    /* Filesystem locations */
    PyUnicodeObject *program_name;
    PyUnicodeObject *executable;
    PyUnicodeObject *prefix;           /* PYTHONHOME */
    PyUnicodeObject *exec_prefix;      /* PYTHONHOME */
    PyUnicodeObject *base_prefix;      /* pyvenv.cfg */
    PyUnicodeObject *base_exec_prefix; /* pyvenv.cfg */

    /* Site module */
    PyBoolObject *enable_site_config;  /* -S switch (inverted) */
    PyBoolObject *no_user_site;        /* -s switch, PYTHONNOUSERSITE */

    /* Import configuration */
    PyBoolObject *dont_write_bytecode; /* -B switch, PYTHONDONTWRITEBYTECODE */
    PyBoolObject *ignore_module_case;  /* PYTHONCASEOK */
    PyListObject *import_path;        /* PYTHONPATH (etc) */

    /* Standard streams */
    PyBoolObject    *use_unbuffered_io; /* -u switch, PYTHONUNBUFFEREDIO */
    PyUnicodeObject *stdin_encoding;    /* PYTHONIOENCODING */
    PyUnicodeObject *stdin_errors;      /* PYTHONIOENCODING */
    PyUnicodeObject *stdout_encoding;   /* PYTHONIOENCODING */
    PyUnicodeObject *stdout_errors;     /* PYTHONIOENCODING */
    PyUnicodeObject *stderr_encoding;   /* PYTHONIOENCODING */
    PyUnicodeObject *stderr_errors;     /* PYTHONIOENCODING */

    /* Filesystem access */
    PyUnicodeObject *fs_encoding;

    /* Debugging output */
    PyBoolObject *debug_parser;    /* -d switch, PYTHONDEBUG */
    PyLongObject *verbosity;       /* -v switch */

    /* Code generation */
    PyLongObject *bytes_warnings;  /* -b switch */
    PyLongObject *optimize;        /* -O switch */

    /* Signal handling */
    PyBoolObject *install_signal_handlers;

    /* Implicit execution */
    PyUnicodeObject *startup_file;  /* PYTHONSTARTUP */

    /* Main module
     *
     * If prepare_main is set, at most one of the main_* settings should
     * be set before calling PyRun_PrepareMain (Py_ReadMainInterpreterConfig
     * will set one of them based on the command line arguments if
     * prepare_main is non-zero when that API is called).
    PyBoolObject    *prepare_main;
    PyUnicodeObject *main_source; /* -c switch */
    PyUnicodeObject *main_path;   /* filesystem path */
    PyUnicodeObject *main_module; /* -m switch */
    PyCodeObject    *main_code;   /* Run directly from a code object */
    PyObject        *main_stream; /* Run from stream */
    PyBoolObject    *run_implicit_code; /* Run implicit code during prep */

    /* Interactive main
     *
     * Note: Settings related to interactive mode are very much in flux.
     */
    PyObject *prompt_stream;      /* Output interactive prompt */
    PyBoolObject *show_banner;    /* -q switch (inverted) */
    PyBoolObject *inspect_main;   /* -i switch, PYTHONINSPECT */

} PyConfigAsObjects;

PyInterpreterConfig구조체에는 주 인터프리터와 서브인터프리터 사이에서 달라질 수 있는 설정이 들어 있습니다. 주 인터프리터의 경우 이러한 설정은 Py_InitializeMainInterpreter()에 의해 자동으로 채워집니다.

typedef struct {
    PyBoolObject *is_main_interpreter;    /* Easily check for subinterpreters */
} PyInterpreterConfig;

이러한 구조체는 객체 포인터만으로 구성되므로 명시적인 초기화 프로그램 정의가 필요하지 않습니다. C99에서 구조체 메모리를 0으로 기본 초기화하는 것으로 충분합니다.

주 인터프리터 초기화 완료

초기화 과정의 마지막 단계는 구성 설정을 실제로 적용하고 주 인터프리터의 부트스트랩을 완전한 동작 상태까지 완료하는 것입니다.:

int Py_InitializeMainInterpreter(const PyConfigAsObjects *config);

Py_BuildPythonConfig와 마찬가지로 구성 데이터에 문제가 발견되면 이 호출은 치명적 오류를 발생시키는 대신 예외를 발생시키고 오류 반환을 보고합니다. (확인 필요: 기존 공개 초기화 API는 오류 없이 여러 번 호출할 수 있으며, 한 번만 쓸 수 있는 설정의 변경 사항은 단순히 무시합니다. 기존 API보다 새 API를 더 엄격하게 만들려고 하기보다는 해당 동작을 유지하는 것이 합리적일 수 있습니다.)

모든 구성 설정이 필요하므로 구성 구조체는 항상 Py_BuildPythonConfig를 통해 전달하여 완전히 채워야 합니다.

호출이 성공하면 Py_IsInitialized()는 true가 되고 Py_IsInitializing()는 false가 됩니다. 핵심 런타임만 초기화된 단계의 인터프리터에 대해 위에서 설명한 주의 사항은 더 이상 적용되지 않습니다.

Py_IsInitialized()가 true일 때 Py_InitializeMainInterpreter()를 다시 호출하려고 하면 오류입니다.

그러나 __main__ 모듈과 관련된 일부 메타데이터는 여전히 불완전할 수 있습니다.

  • sys.argv[0]은 아직 최종 값을 갖지 않을 수 있습니다.
    • CPython으로 모듈 또는 패키지를 실행할 때는 -m이 됩니다.
    • 유효한 sys.path 항목을 실행할 때는 __main__모듈의 위치가 아니라 sys.path[0]과 동일합니다. (일반적으로 zip 파일 또는 디렉터리입니다.)
    • 그 밖의 경우에는 정확합니다.
      • 일반 스크립트를 실행하는 경우에는 스크립트 이름입니다.
      • 제공된 문자열을 실행하는 경우에는 -c입니다.
      • 표준 입력에서 실행하는 경우에는 - 또는 빈 문자열입니다.
  • __main__ 모듈의 메타데이터는 여전히 해당 모듈이 내장 모듈임을 나타냅니다.

이 함수는 일반적으로 마지막 작업으로 site를 암시적으로 가져옵니다. (이때 Py_IsInitialized()는 이미 설정되어 있습니다.) 구성 설정에서 “enable_site_config” 플래그를 Py_False로 설정하면 이 동작이 비활성화되며, 프로세스에서 나중에 import site를 명시적으로 실행하더라도 전역 상태에 대한 부작용이 모두 제거됩니다.

메인 모듈 준비

Note

관련 PEP 587에서는 PyRun_PrepareMainPyRun_ExecMain이 따로 노출되지 않으며, 대신 메인을 준비하고 실행한 다음 파이썬 인터프리터를 종료하는 Py_RunMain API를 통해 접근합니다.

이 하위 단계에서는 실제로 __main__ 모듈 코드의 실행을 시작하지 않고 __main__ 모듈 관련 메타데이터를 완성합니다.

다음 API를 호출하여 처리합니다.:

int PyRun_PrepareMain();

이 작업은 메인 인터프리터에서만 허용되며, 현재 스레드 상태가 서브인터프리터에 속하는 스레드에서 호출하면 RuntimeError가 발생합니다.

실제 처리는 구성 구조체의 일부로 인터프리터 상태에 저장된 메인 관련 설정에 따라 수행됩니다.

prepare_main이 0이면 이 호출은 아무 작업도 수행하지 않습니다.

main_source, main_path, main_module, main_streammain_code가 모두 NULL이면 이 호출은 아무 작업도 수행하지 않습니다.

main_source, main_path, main_module, main_stream 또는 main_code 중 둘 이상이 설정되어 있으면 RuntimeError가 보고됩니다.

main_code가 이미 설정되어 있으면 이 호출은 아무 작업도 수행하지 않습니다.

main_stream이 설정되어 있고 run_implicit_code도 설정되어 있으면 startup_file에 지정된 파일을 읽고 컴파일하여 __main__ 네임스페이스에서 실행합니다.

main_source, main_path 또는 main_module이 설정되어 있으면 이 호출은 main_code를 채우는 데 필요한 모든 단계를 수행합니다.

  • main_source의 경우 제공된 문자열을 컴파일하여 main_code에 저장합니다.
  • main_path의 경우:
    • 제공된 경로가 유효한 sys.path 항목으로 인식되면 해당 경로를 sys.path[0]으로 삽입하고, main_module__main__로 설정한 다음 아래의 main_module 처리와 동일하게 처리를 계속합니다.
    • 그렇지 않으면 경로를 CPython 바이트코드 파일로 읽습니다.
    • 읽기에 실패하면 Python 소스 파일로 읽어 컴파일합니다.
    • 후자의 두 경우에는 코드 객체를 main_code에 저장하고 __main__.__file__을 적절히 설정합니다.
  • main_module의 경우:
    • 모든 상위 패키지를 가져옵니다.
    • 모듈의 로더를 결정합니다.
    • 로더가 모듈이 패키지임을 나타내면 main_module의 끝에 .__main__를 추가하고 다시 시도합니다. 최종 이름 세그먼트가 이미 .__main__이면 즉시 실패합니다.
    • 모듈 소스 코드를 찾으면 컴파일된 모듈 코드를 main_code에 저장하고 __main__의 다음 속성을 적절히 설정합니다: __name__, __loader__, __file__, __cached__, __package__.

(참고: 이 절에서 설명하는 동작은 새로운 것이 아니라, 새로운 구성 시스템에 맞게 조정한 CPython 인터프리터의 현재 동작을 기술한 것입니다.)

메인 모듈 실행

Note

관련 PEP 587에서는 PyRun_PrepareMainPyRun_ExecMain이 따로 노출되지 않으며, 대신 메인을 준비하고 실행한 다음 파이썬 인터프리터를 종료하는 Py_RunMain API를 통해 접근합니다.

이 하위 단계에서는 실제 __main__ 모듈 코드의 실행을 처리합니다.

다음 API를 호출하여 처리합니다.:

int PyRun_ExecMain();

이 작업은 메인 인터프리터에서만 허용되며, 현재 스레드 상태가 서브인터프리터에 속하는 스레드에서 호출하면 RuntimeError가 발생합니다.

실제 처리는 구성 구조체의 일부로 인터프리터 상태에 저장된 메인 관련 설정에 따라 수행됩니다.

main_streammain_code가 모두 NULL이면 이 호출은 아무 작업도 수행하지 않습니다.

main_streammain_code가 모두 설정되어 있으면 RuntimeError가 보고됩니다.

main_streamprompt_stream이 모두 설정되어 있으면 주 실행이 새로운 내부 API에 위임됩니다.:

int _PyRun_InteractiveMain(PyObject *input, PyObject* output);

main_stream이 설정되어 있고 prompt_stream이 NULL이면 주 실행이 새로운 내부 API에 위임됩니다.:

int _PyRun_StreamInMain(PyObject *input);

main_code가 설정되어 있으면 주 실행이 새로운 내부 API에 위임됩니다.:

int _PyRun_CodeInMain(PyCodeObject *code);

main 실행이 완료된 후 inspect_main이 설정되어 있거나 PYTHONINSPECT 환경 변수가 설정되어 있으면 PyRun_ExecMain_PyRun_InteractiveMain(sys.__stdin__, sys.__stdout__)을 호출합니다.

구성 데이터의 내부 저장

인터프리터 상태 객체를 확장하여 최소한 PyConfigAsObjectsPyInterpreterConfig 구조체의 내장 복사본을 포함하도록 함으로써, 초기화 중 제공된 구성 설정의 세부 정보가 인터프리터 상태에 포함되도록 업데이트됩니다.

디버깅 목적으로 구성 설정은 sys._configuration 단순 네임스페이스로 노출됩니다 (sys.flagssys.implementation과 유사합니다. 속성 자체는 구성 설정의 두 수준에 대응하는 단순 네임스페이스가 됩니다:

  • all_interpreters
  • active_interpreter

필드 이름은 구성 구조체의 이름과 일치하지만, hash_seed는 의도적으로 제외됩니다.

밑줄이 있는 속성을 의도적으로 선택한 이유는 이러한 구성 설정이 Python 언어 정의의 일부가 아니라 CPython 구현의 일부이기 때문입니다. 표준 라이브러리에서 구현 간 호환성을 지원하기 위해 새로운 설정이 필요하다면, 다른 구현과 합의한 후 PEP 421에 설명된 대로 sys.implementation에 새로운 필수 속성으로 노출해야 합니다.

이는 초기 구성 설정의 스냅샷입니다. 위에서 언급한 경우를 제외하면 런타임 중 인터프리터에 의해 수정되지 않습니다.

서브인터프리터 생성 및 구성

새로운 구성 설정은 인터프리터 상태에 저장되므로, 새로운 서브인터프리터가 생성될 때 이를 초기화해야 합니다. 이는 PyThreadState_Swap(NULL);로 인해 예상보다 까다로운 문제로 밝혀졌습니다(다행히 CPython 자체의 임베딩 테스트에서 이를 실행하므로 개발 중에 이 문제를 감지할 수 있습니다).

이 경우에 대한 간단한 해결책을 제공하기 위해 PEP에서는 새로운 API를 추가할 것을 제안합니다.:

Py_InterpreterState *Py_InterpreterState_Main();

이는 Py_InterpreterState_Head()에 대응하는 API로, 현재 존재하는 인터프리터 중 최신 인터프리터가 아니라 가장 오래된 인터프리터만 보고합니다. 기존 스레드 상태가 있는 스레드에서 Py_NewInterpreter()를 호출하면 해당 스레드의 인터프리터 구성이 새 서브인터프리터를 초기화할 때 사용됩니다. 현재 스레드 상태가 없으면 Py_InterpreterState_Main()의 구성이 사용됩니다.

대신 기존 Py_InterpreterState_Head()API를 사용할 수도 있지만, 서브인터프리터가 생성되고 소멸됨에 따라 해당 참조가 변경되는 반면 PyInterpreterState_Main()은 항상 Py_InitializeRuntime()에서 생성된 초기 인터프리터 상태를 참조합니다.

임베딩 API에 새로운 제약도 추가됩니다. 서브인터프리터가 아직 존재하는 동안 주 인터프리터를 삭제하려고 시도하면 이제 치명적 오류가 발생합니다.

안정 ABI

Python 인터프리터를 임베딩하는 작업은 단순히 확장 모듈을 작성하는 것보다 훨씬 높은 수준의 결합을 수반하므로, 이 PEP에서 제안하는 대부분의 API는 안정 ABI에서 제외됩니다.

안정 ABI에 포함되는 새로 노출된 API는 Py_IsInitializing()Py_IsRuntimeInitialized()쿼리뿐입니다.

빌드 시 구성

이 PEP는 빌드 시 구성 설정의 처리 방식을 변경하지 않으므로 sys.implementation의 내용이나 sysconfig.get_config_vars()의 결과에 영향을 주지 않습니다.

하위 호환성

Py_BuildPythonConfig()가 전역 변수와 환경 변수에 저장된 기존의 모든 구성 설정을 조회하고, Py_InitializeMainInterpreter()가 영향을 받은 설정을 관련 위치에 다시 기록하도록 보장함으로써 하위 호환성이 주로 유지됩니다.

알려진 비호환성 중 하나는 현재 지연 방식으로 읽히는 일부 환경 변수가 인터프리터 초기화 중에 한 번만 읽히게 될 수 있다는 점입니다. 참조 구현이 성숙해짐에 따라, 이러한 사항들은 사례별로 더 자세히 논의될 것입니다. 현재 동적으로 조회되는 것으로 알려진 환경 변수는 다음과 같습니다:

  • PYTHONCASEOK: os.environ['PYTHONCASEOK']에 값을 쓰더라도 더 이상 임포트 시 인터프리터의 파일 이름 대소문자 차이 처리 방식이 동적으로 변경되지 않습니다 (TBC)
  • PYTHONINSPECT: os.environ['PYTHONINSPECT']__main__ 모듈의 실행이 종료된 후에도 계속 확인됩니다

Py_Initialize() 방식의 초기화는 계속 지원될 것입니다. 이는 내부적으로 새 API의 (적어도 일부 요소를) 사용하겠지만, 오늘날과 동일한 동작을 계속 보여줄 것이며, sys.argv는 이후의 PySys_SetArgv 호출이 있기 전까지는 채워지지 않도록 보장할 것입니다 (TBC). 현재 Py_Initialize() 이전에 호출되는 것을 지원하는 모든 API는 계속 그렇게 지원될 것이며, Py_InitializeRuntime() 이전에 호출되는 것도 지원할 것입니다.

시스템 파이썬 실행 파일

시스템에 대한 관리자 권한으로 시스템 유틸리티를 실행할 때, CPython의 기본 동작 중 다수는 바람직하지 않은데, 이는 신뢰할 수 없는 코드가 상승된 권한으로 실행되도록 허용할 수 있기 때문입니다. 가장 문제가 되는 측면은 사용자 사이트 디렉터리가 활성화되어 있고, 환경 변수가 신뢰되며, 실행된 파일이 포함된 디렉터리가 임포트 경로의 맨 앞에 위치한다는 사실입니다.

이슈 16499 [6]는 일반 CPython 실행 파일의 동작을 변경하는 -I 옵션을 추가했지만, 이는 발견하기 어려운 해결책입니다(또한 이미 복잡한 CLI에 또 하나의 옵션을 추가하는 것이기도 합니다). 이 PEP는 그 대신 별도의 system-python 실행 파일을 추가할 것을 제안합니다

현재는 기본 동작이 다른 별도의 실행 파일을 제공하는 것은 유지보수하기가 지나치게 어려울 것입니다. 이 PEP의 목표 중 하나는 유지보수하기 어려운 부트스트래핑 코드의 상당 부분을 좀 더 평범한 CPython 코드로 대체할 수 있게 하는 것과 더불어, 별도의 애플리케이션이 Py_Main의 핵심 구성 요소를 활용하기 더 쉽게 만드는 것입니다. 이 변경 사항을 PEP에 포함시킨 것은 이론상으로는 그럴듯하게 들리지만 실제로는 문제가 있는 것으로 드러나는 설계가 채택되는 것을 피하는 데 도움을 주기 위해 고안되었습니다.

이러한 종류의 “대체 CLI”를 깔끔하게 지원하는 것이 CPython이 지원하는 서로 다른 실행 모드 중에서 결정하는 핵심 로직을 더 잘 노출하도록 제안된 변경의 주된 이유입니다:

  • 스크립트 실행
  • 디렉터리/zip 파일 실행
  • 명령 실행 (“-c” 스위치)
  • 모듈 또는 패키지 실행 (“-m” 스위치)
  • 표준 입력으로부터의 실행(비대화형)
  • 대화형 표준 입력

실제로 이를 구현하면 초기화 단계에서 사용할 더 나은 인자 파싱 인프라스트럭처가 필요하다는 것이 드러날 수도 있습니다.

미해결 질문

  • Py_BuildPythonConfigPy_InitializeMainInterpreter에 대한 오류 세부 사항(구현이 진행됨에 따라 더 명확해질 것입니다)

구현

참조 구현은 (독립적인 프로젝트로 유지하려는 시도가 비실용적인 것으로 드러남에 따라) CPython 참조 인터프리터 내에서 비공개 API 리팩터링으로 개발되고 있습니다.

PEP 587은 이 제안 중 충분히 안정적이어서 Python 3.8의 공개 API로 제안할 가치가 있다고 여겨지는 부분을 추출합니다.

현재 상태 (Python 3.6 기준)

인터프리터를 설정하는 현재의 메커니즘들은 지난 20여 년에 걸쳐 상당히 임기응변적인 방식으로 축적되어 왔으며, 그 결과 문서화 수준이 제각각인 다소 일관성 없는 인터페이스가 되었습니다.

기존 설정들과 그 처리 방식에 대한 더 자세한 논의는 PEP 587도 참고하십시오.

(참고: 아래 정보 중 일부는 정리해서 3.x용 C API 문서에 추가하는 것이 좋을 듯합니다 - 이는 모두 CPython에 특화된 내용이므로 언어 참조서에는 어울리지 않습니다)

환경 변수 무시하기

-E 명령줄 옵션을 사용하면 Python 인터프리터를 초기화할 때 모든 환경 변수를 무시할 수 있습니다. 임베딩 애플리케이션은 Py_Initialize()를 호출하기 전에 Py_IgnoreEnvironmentFlag를 설정하여 이 동작을 활성화할 수 있습니다.

CPython 소스 코드에서 Py_GETENV 매크로는 암묵적으로 이 플래그를 검사하며, 플래그가 설정되어 있으면 항상 NULL을 반환합니다.

<TBD: PYTHONCASEOK는 이 설정과 무관하게 확인되는 것으로 알고 있습니다> <TBD: -E는 Windows 레지스트리 키도 무시합니까?>

무작위 해싱

무작위 해싱은 (3.3 이전 릴리스에서는) -R 명령줄 옵션과 PYTHONHASHSEED 환경 변수를 통해 제어됩니다.

Python 3.3에서는 환경 변수만 유효합니다. 이는 무작위 해싱을 비활성화하거나(시드 값 0을 사용하여), 특정 해시 값을 강제하는 데(예: 테스트의 재현성을 위해, 또는 프로세스 간에 해시 값을 공유하기 위해) 사용할 수 있습니다.

그러나 임베딩 애플리케이션은 해시 무작위화를 명시적으로 요청하기 위해 Py_HashRandomizationFlag를 사용해야 합니다(CPython은 이를 Py_Initialize()가 아닌 Py_Main()에서 설정합니다).

새로운 설정 API를 사용하면 임베딩 애플리케이션이 다른 수단(예: 설정 파일 또는 별도의 환경 변수)으로 제공되는 텍스트 기반 설정값으로 PYTHONHASHSEED 처리를 재사용하기가 수월해질 것입니다.

Python과 표준 라이브러리 찾기

Python 바이너리와 표준 라이브러리의 위치는 여러 요소의 영향을 받습니다. 이 계산을 수행하는 데 사용되는 알고리즘은 소스 코드 [3], [4] 외에는 어디에도 문서화되어 있지 않습니다. 그 설명조차도 Python 3.3에서 추가된 가상 환경 지원(자세한 내용은 PEP 405 참조)에 맞게 갱신되지 않아 불완전합니다.

이 계산은 다음과 같은 함수 호출(Py_Initialize() 호출 이전에 이루어진)과 환경 변수의 영향을 받습니다:

  • Py_SetProgramName()
  • Py_SetPythonHome()
  • PYTHONHOME

또한 파일 시스템에서 pyvenv.cfg 파일(PEP 405 참조)이 있는지, 없으면 lib/os.py (Windows) 또는 lib/python$VERSION/os.py 파일이 있는지 검사합니다.

PREFIXEXEC_PREFIX의 빌드 시점 설정도 관련이 있으며, Windows의 일부 레지스트리 설정 역시 그러합니다. 하드코딩된 폴백은 소스 체크아웃 상태에서 작업할 때의 CPython 소스 트리 구조와 빌드 출력을 기반으로 합니다.

sys.path 구성하기

임베딩 애플리케이션은 sys.path의 계산을 완전히 재정의하기 위해 Py_Initialize() 이전에 Py_SetPath()를 호출할 수 있습니다. 계산 중 일부만 허용하는 것은 간단하지 않은데, 초기화가 이미 완료된 후에 sys.path를 수정하면 시작 순서 동안 표준 라이브러리 모듈이 임포트될 때 그 수정 사항이 적용되지 않기 때문입니다.

Py_GetPath()의 첫 호출(Py_Initialize()에 암묵적으로 포함됨) 이전에 Py_SetPath()가 사용되지 않으면, 위의 위치 데이터 계산을 기반으로 PYTHONPATH 환경 변수와 함께 적절한 경로 항목을 계산합니다.

<TBD: Windows에서는 레지스트리와 관련된 여러 작업도 있습니다>

시작 시 암묵적으로 임포트되는(-S 옵션으로 비활성화하지 않는 한) site 모듈은 문서 [5]에 설명된 대로 이 초기 경로 집합에 추가 경로를 더합니다.

-s 명령줄 옵션을 사용하면 추가되는 디렉터리 목록에서 사용자 사이트 디렉터리를 제외할 수 있습니다. 임베딩 애플리케이션은 Py_NoUserSiteDirectory 전역 변수를 설정하여 이를 제어할 수 있습니다.

다음 명령을 사용하면 특정 시스템에서 특정 Python 실행 파일의 기본 경로 구성을 확인할 수 있습니다:

  • ./python -c "import sys, pprint; pprint.pprint(sys.path)" - 표준 구성
  • ./python -s -c "import sys, pprint; pprint.pprint(sys.path)" - 사용자 사이트 디렉터리 비활성화
  • ./python -S -c "import sys, pprint; pprint.pprint(sys.path)" - 모든 사이트 경로 수정 비활성화

(참고: -c 대신 -m site를 사용해도 비슷한 정보를 볼 수 있지만, 모든 경로 항목에 대해 os.abspath를 호출하여 상대 경로 항목을 절대 경로처럼 보이게 만들기 때문에 다소 오해의 소지가 있습니다. 마지막 경우에는 site 모듈을 사용해도 문제가 발생하는데, Python 3.3 이전 버전에서는 site를 명시적으로 임포트하면 -S가 피하는 경로 수정이 수행되는 반면, 3.3 이상에서는 -m site-S와 함께 사용하면 현재 실패합니다)

sys.path[0]의 계산은 비교적 간단합니다:

  • 일반 스크립트(Python 소스 또는 컴파일된 바이트코드)의 경우, sys.path[0]은 스크립트가 포함된 디렉터리가 됩니다.
  • 유효한 sys.path 항목(일반적으로 zip 파일 또는 디렉터리)의 경우, sys.path[0]은 해당 경로가 됩니다
  • 대화형 세션에서 실행하거나, 표준 입력에서 실행하거나, -c 또는 -m 스위치를 사용하는 경우, sys.path[0]은 빈 문자열이 되며, 임포트 시스템은 이를 현재 디렉터리에서의 임포트를 허용하는 것으로 해석합니다

sys.argv 구성

이 PEP에서 논의된 다른 대부분의 설정과 달리, sys.argvPy_Initialize()에 의해 암묵적으로 설정되지 않습니다. 대신, Py_SetArgv()에 대한 명시적인 호출을 통해 설정해야 합니다.

CPython은 Py_Initialize()를 호출한 후 Py_Main()에서 이를 호출합니다. sys.argv[1:]의 계산은 간단합니다: 이는 스크립트 이름 뒤에 전달된 명령줄 인자들이거나 -c 또는 -m 옵션의 인자입니다.

sys.argv[0]의 계산은 조금 더 복잡합니다:

  • 일반적인 스크립트(소스 또는 바이트코드)의 경우, 스크립트 이름이 됩니다
  • sys.path 항목(일반적으로 zip 파일 또는 디렉터리)의 경우, 처음에는 zip 파일 또는 디렉터리 이름이 되지만, 이후 runpy 모듈에 의해 임포트된 __main__ 모듈의 전체 경로로 변경됩니다.
  • -m 스위치로 지정된 모듈의 경우, 처음에는 문자열 "-m"이 되지만, 이후 runpy 모듈에 의해 실행된 모듈의 전체 경로로 변경됩니다.
  • -m 스위치로 지정된 패키지의 경우, 처음에는 문자열 "-m"이 되지만, 이후 runpy 모듈에 의해 해당 패키지의 실행된 __main__ 서브모듈의 전체 경로로 변경됩니다.
  • -c로 실행된 명령의 경우, 문자열 "-c"가 됩니다
  • 표준 입력에서 명시적으로 요청된 입력의 경우, 문자열 "-"가 됩니다
  • 그 외의 경우, 빈 문자열이 됩니다

임베딩 애플리케이션은 직접 Py_SetArgv를 호출해야 합니다. 이를 수행하는 CPython 로직은 Py_Main()의 일부이며 별도로 노출되지 않습니다. 하지만 runpy 모듈은 runpy.run_modulerunpy.run_path에서 대략적으로 동등한 로직을 제공합니다.

기타 구성 설정

TBD: 다음 항목의 초기화를 더 자세히 다룰 예정입니다:

  • 임포트 시스템을 완전히 비활성화하기
  • 초기 경고 시스템 상태:
    • sys.warnoptions
    • (-W 옵션, PYTHONWARNINGS)
  • 임의의 확장 옵션(예: faulthandler를 자동으로 활성화하기 위한 옵션):
    • sys._xoptions
    • (-X 옵션)
  • 다음에서 사용되는 파일 시스템 인코딩:
    • sys.getfsencoding
    • os.fsencode
    • os.fsdecode
  • 다음에서 사용되는 IO 인코딩 및 버퍼링:
    • sys.stdin
    • sys.stdout
    • sys.stderr
    • (-u 옵션, PYTHONIOENCODING, PYTHONUNBUFFEREDIO)
  • 바이트코드 파일을 암묵적으로 캐시할지 여부:
    • sys.dont_write_bytecode
    • (-B 옵션, PYTHONDONTWRITEBYTECODE)
  • 대소문자를 구분하지 않는 플랫폼에서 파일명의 올바른 대소문자를 강제할지 여부
    • os.environ["PYTHONCASEOK"]
  • sys.flags에 노출되는 그 밖의 설정들:
    • debug (pgen 파서에서 디버깅 출력을 활성화)
    • inspect (__main__ 종료 후 대화형 인터프리터로 진입)
    • interactive (stdin을 tty로 취급)
    • optimize (__debug__ 상태, .pyc 또는 .pyo 작성, 문서 문자열 제거)
    • no_user_site (사용자 사이트 디렉터리를 sys.path에 추가하지 않음)
    • no_site (시작 시 site를 암묵적으로 임포트하지 않음)
    • ignore_environment (설정 시 환경 변수 사용 여부)
    • verbose (온갖 잡다한 출력을 활성화)
    • bytes_warning (암묵적인 str/bytes 상호작용에 대한 경고/오류)
    • quiet(verbose가 활성화되어 있거나 표준 입력이 tty이고 인터프리터가 대화형 모드로 실행되더라도 배너 출력을 비활성화합니다)
  • CPython의 시그널 처리기를 설치할지 여부입니다

현재 CPython 설정의 대부분은 C 수준의 전역 변수를 통해 처리됩니다:

Py_BytesWarningFlag (-b)
Py_DebugFlag (-d option)
Py_InspectFlag (-i option, PYTHONINSPECT)
Py_InteractiveFlag (property of stdin, cannot be overridden)
Py_OptimizeFlag (-O option, PYTHONOPTIMIZE)
Py_DontWriteBytecodeFlag (-B option, PYTHONDONTWRITEBYTECODE)
Py_NoUserSiteDirectory (-s option, PYTHONNOUSERSITE)
Py_NoSiteFlag (-S option)
Py_UnbufferedStdioFlag (-u, PYTHONUNBUFFEREDIO)
Py_VerboseFlag (-v option, PYTHONVERBOSE)

위 변수들의 경우, 명령줄 옵션과 환경 변수를 C 전역 변수로 변환하는 작업은 Py_Main이 처리하므로, 각 임베딩 애플리케이션은 기본값을 변경하려면 이를 적절히 설정해야 합니다.

일부 설정은 OS 수준의 환경 변수로만 제공될 수 있습니다:

PYTHONSTARTUP
PYTHONCASEOK
PYTHONIOENCODING

Py_InitializeEx() API는 또한 CPython의 시그널 처리기를 설치할지 여부를 나타내는 불리언 플래그를 받습니다.

마지막으로, 도입 배너 출력과 같은 일부 대화형 동작은 운영 체제가 표준 입력을 터미널 연결로 보고할 때만 트리거됩니다.

TBD: “-x” 옵션이 어떻게 처리되는지(메인 스크립트의 첫 번째 주석 줄 처리를 건너뜁니다) 문서화할 것

[1]에서 자세한 작업 순서 노트도 참고하십시오.

참고 문헌