Python의 C API 변경하기

C API는 다음 계층으로 나뉩니다:

  1. Py_BUILD_CORE가 정의되어 있을 때 사용할 수 있는 내부 비공개 API입니다. 이상적으로는 Include/internal/에 선언합니다. 이름이 밑줄로 시작하는 모든 API도 비공개로 간주합니다.

  2. PyUnstable_ 이름 접두사로 식별되는 불안정 C API입니다. 이상적으로는 일반 공개 API와 함께 Include/cpython/에 선언합니다.

  3. 관련 Include/Python.h를 일반적인 방식으로 포함할 때 사용할 수 있는 “일반” 공개 C API입니다. 이상적으로는 Include/cpython/에 선언합니다.

  4. 관련 Py_LIMITED_API가 정의되어 있을 때 사용할 수 있는 제한 C API입니다. 이상적으로는 Include/ 바로 아래에 선언합니다.

각 계층에는 정의를 추가하거나 변경할 때 고려해야 할 서로 다른 안정성 및 유지관리 요구 사항이 있습니다.

공개 C API의 공개 하위 호환성 보장은 사용자 문서인 Doc/c-api/stable.rst (C API Stability)에 설명되어 있습니다. C 언어 호환성 보장은 Doc/c-api/intro.rst (Introduction)에 설명되어 있습니다.

코어 개발자는 공개적으로 약속하는 수준보다 호환성에 더 주의해야 합니다. 자세한 내용은 공개 C API를 참조하십시오.

내부 API

내부 API는 Include/internal/에 정의되며, Py_BUILD_CORE와 같은 매크로가 나타내듯이 CPython 자체를 빌드할 때만 사용할 수 있습니다.

내부 API는 언제든지 변경할 수 있지만, 다른 API나 다른 CPython 개발자가 이에 의존할 수 있으므로 안정적으로 유지하는 것이 좋습니다. 사용자에게 내부 API는 때때로 까다로운 문제를 해결하는 최선의 우회 방법입니다. — 하지만 지원되는 방식으로 이러한 사용 사례를 처리할 방법을 찾을 수 있도록 해당 사용 사례를 C API Discourse category나 이슈에서 논의해야 합니다.

PyAPI_FUNC 또는 PyAPI_DATA 사용

PyAPI_FUNC 또는 PyAPI_DATA로 정의된 Include/internal/ 내의 함수나 구조체는 디버거나 프로파일러와 같은 특정 사용 사례에만 노출되는 내부 함수입니다. 이상적으로는 이를 불안정 C API로 마이그레이션해야 합니다.

extern 키워드 사용

extern 키워드로 정의된 Include/internal/ 내의 함수는 CPython 코드베이스 외부에서 사용해서는 안 되며 사용할 수도 없습니다. Py_BUILD_CORE_BUILTIN 매크로를 정의하여 빌드한 내장 표준 라이브러리 확장만 이러한 함수를 사용할 수 있습니다.

확실하지 않다면 새로운 내부 C 함수는 extern 키워드를 사용하여 Include/internal에 정의해야 합니다.

비공개 이름

이름이 밑줄로 시작하는 모든 API도 내부 API로 간주합니다. 현재는 정의를 Include/internal/에 배치하거나 .c 파일에 직접 배치하는 대신 이러한 이름을 사용하는 주요 사용 사례가 하나뿐입니다:

  • 사용자가 직접 호출해서는 안 되는, 다른 공개 API를 위한 내부 도우미입니다.

역사적으로는 불안정 C API가 더 적합한 API에도 밑줄을 사용했다는 점에 유의하십시오:

  • 새 API의 실제 사용을 테스트하기 위해 Python 릴리스에 포함되는 “잠정적” API;

  • JIT 컴파일러처럼 매우 특수한 용도를 위한 API입니다.

내부 API 테스트

내부 C API의 C 테스트는 Modules/_testinternalcapi.c에 있습니다. 이름이 test_*인 함수는 테스트로 직접 사용됩니다. 테스트의 Python 부분은 Lib/test 내 여러 위치에 있습니다.

공개 C API

CPython의 공개 C API는 Python.h를 일반적인 방식으로 포함할 때(즉, 다른 변형을 선택하는 매크로를 정의하지 않을 때) 사용할 수 있습니다.

제한된 API의 일부가 아니라면 아래에서 설명하는 대로 Include/cpython/에 정의해야 합니다.

새로운 공개 API를 추가하기 전에 C API 워킹 그룹decisions repo에서 문의하십시오. 이는 새로 추가된 API가 일관되고 유지 관리 가능한지 확인하는 데 도움이 됩니다.

또한 C99에 없는 C 기능을 요구하기 전에 C API WG와 협의하십시오. 공개 문서는 C11과의 호환성만 보장하지만, 실제로는 필요한 경우에만 C11 기능을 개별적으로 도입합니다.

공개 API 확장/변경 지침

  • 새 API가 참조 횟수 계산 규칙을 따르는지 확인하십시오. (이 규칙을 따르면 API의 동작을 더 쉽게 추론할 수 있고, 다른 Python 구현에서도 더 쉽게 사용할 수 있습니다.)

    • 함수는 참조를 절대로 훔쳐서는 안 됩니다.

    • 함수는 빌린 참조를 절대로 반환해서는 안 됩니다.

    • 참조를 반환하는 함수는 반드시 강한 참조를 반환해야 합니다.

  • 해당하는 모든 구조체 필드, 인자 및 반환 값의 소유권 규칙과 수명이 명확히 정의되었는지 확인하십시오.

  • PyObject *를 반환하는 함수는 성공 시 유효한 포인터를 반환해야 하며, 오류 시 예외를 발생시키고 NULL을 반환해야 합니다. 대부분의 다른 API는 오류 시 예외를 발생시키고 -1을 반환해야 하며, 성공 시 0을 반환해야 합니다.

  • 대소 결과를 반환하는 API는 더 작은 결과에 대해 0을 반환하고, 더 큰 결과에 대해 1을 반환해야 합니다. 다음과 같이 세 가지 반환 값을 갖는 조회 함수를 살펴보겠습니다.

    • return -1: 내부 오류 또는 API 오용이며, 예외가 발생합니다.

    • return 0: 조회에 성공했지만 항목을 찾지 못했습니다.

    • return 1: 조회에 성공했으며 항목을 찾았습니다.

이 지침을 API에 적용할 수 없다면 공개 논의를 시작하십시오.

참고

반환 값이란 C 반환 문이 반환하는 값을 뜻합니다.

C API 테스트

공개 C API의 테스트는 _testcapi 모듈에 있습니다. 이름이 test_*인 함수는 테스트로 직접 사용됩니다. Python 코드가 필요하거나 일부를 Python으로 작성하는 편이 더 쉬운 테스트는 Lib/test 내에 있으며, 주로 Lib/test/test_capi에 있습니다.

크기 때문에 _testcapi 모듈은 여러 소스 파일에 정의되어 있습니다. 새로운 테스트 세트를 추가하려면(또는 단일 Modules/_testcapimodule.c에서 테스트 세트를 추출하려면):

  • Modules/_testcapi/yourfeature.c C 파일을 생성하십시오.

  • 다음 사항을 제외하고, 이 파일은 평소와 같이 모듈을 정의해야 합니다.

    • <Python.h> 대신 "parts.h"를 포함하십시오.

    • PyInit_modname 대신 _testcapi 모듈을 받아 함수/클래스를 추가하는 _PyTestCapi_Init_yourfeature 함수를 정의하십시오. (함수를 추가하려면 PyModule_AddFunctions를 사용할 수 있습니다.)

  • _PyTestCapi_Init_* 함수를 Modules/_testcapi/parts.h에 추가하십시오.

  • Modules/_testcapimodule.cPyInit__testcapi에서 _PyTestCapi_Init_*를 호출하십시오.

  • 다른 _testcapi/*.c 항목과 함께 새 C 파일을 Modules/Setup.stdlib.in, PCbuild/_testcapi.vcxproj, PCbuild/_testcapi.vcxproj.filters에 추가하십시오.

모든 Modules/_testcapi/*.c 소스가 같은 모듈을 초기화하므로 이름 충돌에 주의하십시오.

기존 테스트를 옮길 때 실제로 사용자 정의 예외를 테스트하는 경우가 아니라면 TestErrorPyExc_AssertionError로 자유롭게 대체해도 됩니다.

불안정 C API

불안정 C API 계층은 디버거 및 JIT 컴파일러처럼 인터프리터와 긴밀하게 통합해야 하는 확장을 위한 것입니다. 이 계층의 사용자는 기능 릴리스마다 코드를 변경해야 할 수 있습니다.

여러 측면에서 이 계층은 일반 C API와 비슷합니다.

  • Python.h를 일반적인 방식으로 포함하면 사용할 수 있습니다.

  • 관련 Include/cpython/에 정의해야 합니다.

  • 의도치 않게 손상하지 않도록 테스트가 필요합니다.

  • 예상되는 동작에 관해 개발자와 사용자 모두가 합의할 수 있도록 문서가 필요합니다.

  • 동일한 방식으로 테스트되고 문서화됩니다.

차이점은 다음과 같습니다.

  • 함수, 구조체, 매크로 등의 이름은 PyUnstable_ 접두사로 시작합니다. 이를 통해 불안정 계층에 속하는 항목을 정의합니다.

  • 불안정 API는 폐지 예정 기간 없이 기능 릴리스에서 변경될 수 있습니다.

  • 문서에 안정성 관련 참고 사항이 표시됩니다. 이는 이름을 기반으로 자동으로 이루어집니다(Doc/tools/extensions/c_annotations.py를 통함).

“불안정”이라는 이름에도 불구하고 서드파티 코드가 이 API를 안정적으로 사용할 수 있도록 하는 규칙이 있습니다.

  • 기능 릴리스(3.x.03.x.0의 알파와 베타 포함)에서 변경 및 제거할 수 있습니다.

  • 기존 기능을 위한 새로운 불안정 API는 베타 기능 동결 이후에도 첫 번째 릴리스 후보까지 추가할 수 있습니다. 베타 기간에는 Core Development Discourse에서 합의해야 합니다.

  • 하위 호환성이 없는 변경은 기존 C 호출자의 컴파일이 실패하게 해야 합니다. 예를 들어 인자를 추가하거나 제거하거나, 함수 이름을 변경해야 합니다.

  • API를 불안정 계층으로 옮기거나 불안정 계층에서 다른 계층으로 옮길 때는 호환되지 않는 변경이 이루어질 때까지 이전 이름을 계속 사용할 수 있어야 하지만 지원 중단 예정으로 표시해야 합니다. 다시 말해 호출 코드를 중단시킬 수는 있지만, 불필요하게 중단시켜서는 안 됩니다.

API를 공개 계층에서 불안정 계층으로 옮기기

  • PyUnstable_ 접두사를 붙인 새 이름으로 API를 노출하십시오. 모든 심볼(함수, 매크로, 변수 등)에 PyUnstable_ 접두사를 사용해야 합니다.

  • 이전 이름을 별칭으로 만드십시오(예: 새 함수를 호출하는 static inline 함수).

  • 일반적으로 Py_DEPRECATED를 사용하여 이전 이름을 지원 중단 예정으로 표시하십시오.

  • “새 소식”에 변경 사항을 공지하십시오.

호환되지 않는 변경이 이루어질 때까지 이전 이름을 계속 사용할 수 있어야 합니다. 파이썬의 하위 호환성 정책(PEP 387)에 따라, 이 폐지 예정은 최소 두 번의 릴리스 동안 지속되어야 합니다(운영 위원회의 예외는 제외).

공식 불안정 계층이 추가된 Python 3.12 이전 버전에서 도입된 API에는 완화된 규칙이 적용됩니다. Python 3.12 이전에 도입되었으며 다음 중 하나에 해당하는 API에는 해당 함수가 이미 불안정 계층의 일부였던 것처럼 호환되지 않는 변경을 적용하고 이전 이름을 제거할 수 있습니다.

  • 기본값보다 안정성이 낮다고 문서화되어 있습니다.

  • 이름이 밑줄로 시작합니다.

API를 비공개 계층에서 불안정 계층으로 옮기기

  • PyUnstable_ 접두사를 붙인 새 이름으로 API를 노출하십시오.

  • 이전 이름이 문서화되어 있거나 외부에서 널리 사용된다면 별칭으로 만들고 지원 중단 예정으로 표시하십시오(일반적으로 Py_DEPRECATED를 사용합니다). 이전에 공개되어 있었던 것처럼 호환되지 않는 변경이 이루어질 때까지 계속 사용할 수 있어야 합니다.

    이는 밑줄로 시작하는 이름에도 적용됩니다. Python이 항상 선행 밑줄을 엄격하게 다룬 것은 아닙니다.

  • 새 소식에 변경 사항을 공지하십시오.

API를 불안정 계층에서 공개 계층으로 옮기기

  • PyUnstable_ 접두사가 없는 새 이름으로 API를 노출하십시오.

  • 이전 PyUnstable_* 이름을 별칭으로 만드십시오(예: 새 함수를 호출하는 static inline 함수).

  • 새 소식에 변경 사항을 공지하십시오.

새 공개 이름이 지원 중단 예정으로 표시되거나 제거될 때까지 이전 이름을 계속 사용할 수 있어야 합니다. 이전 이름은 처음부터 불안정했으므로 지원 중단 예정으로 표시할 필요가 없지만, 어떤 함수가 이제 더 광범위한 사용자를 대상으로 제공할 준비가 되었다는 이유만으로 작동하는 코드를 중단시킬 필요도 없습니다.

제한된 API

제한된 API는 Python 3 버전 간의 ABI 안정성을 보장하도록 설계된 C API의 하위 집합입니다. Py_LIMITED_API 매크로를 정의하면 노출되는 API가 이 하위 집합으로 제한됩니다.

안정 ABI를 깨뜨리는 변경은 허용되지 않습니다.

제한된 API는 cpythoninternal 하위 디렉터리를 제외한 Include/ 안에 정의해야 합니다.

제한된 API를 변경하고 항목을 제거하기 위한 지침

안정 ABI를 위반해서는 안 되지만, 다음 조건을 충족하면 기존 제한된 API를 변경하고 항목을 제거할 수 있습니다:

  • 하위 호환성 정책(PEP 387)을 준수하고,

  • 안정 ABI를 위반하지 않아야 합니다 – 즉, 이전 Python 버전의 제한된 API로 컴파일된 확장이 최신 Python 버전에서도 계속 작동해야 합니다.

이는 처리하기 까다로우며 신중하게 생각해야 합니다. 몇 가지 예는 다음과 같습니다:

  • 제한된 API의 어느 버전에서든 매크로를 통해 접근하는 함수, 구조체 등은 밑줄로 시작하는 이름을 사용하더라도 안정 ABI의 일부입니다. 이러한 항목을 제거해서는 안 되며 시그니처도 변경해서는 안 됩니다. (단, 구현은 변경할 수 있습니다.)

  • 구조체 멤버가 제한된 API의 어느 버전에든 포함된 적이 있다면 순서를 바꿀 수 없습니다.

  • 제한된 API에서 사용자가 구조체를 직접 할당할 수 있도록 허용한다면 그 크기를 변경해서는 안 됩니다.

  • 내보낸 심볼(함수 및 데이터)은 내보낸 심볼로 계속 사용할 수 있어야 합니다. 구체적으로, Python이 실제 함수도 계속 제공하는 경우에만 함수를 static inline 함수(또는 매크로)로 변환할 수 있습니다. 예시는 3.10의 Py_NewRef macroredefinition을 참조하십시오.

안정 ABI의 일부로 표시된 항목도 제거할 수 있지만, 이전 제한된 API의 어떤 버전에서도 해당 항목을 사용할 방법이 없었던 경우에만 가능합니다.

제한된 API에 추가하기 위한 지침

  • 일반적인 공개 C API에 대한 지침이 적용됩니다. 관련 공개 API 확장/변경 지침를 참조하십시오.

  • 새 제한된 API는 Py_LIMITED_API가 해당 API가 추가된 버전 이상으로 설정된 경우에만 정의해야 합니다. (올바른 #if 가드는 아래를 참조하십시오.)

  • 모든 매개변수 타입, 반환값, 구조체 멤버 등은 제한된 API의 일부여야 합니다.

    • FILE*(또는 ABI 이식성 문제가 있는 다른 타입)을 다루는 함수는 추가해서는 안 됩니다.

  • 매크로를 정의할 때는 다시 한번 신중하게 생각하십시오.

    • 매크로는 구현 세부 사항을 노출해서는 안 됩니다

    • 함수는 함수형 매크로로만 제공하지 말고 실제 함수로 내보내야 합니다.

    • 가능하면 매크로를 사용하지 마십시오. 그러면 C 전처리기를 사용하지 않는 언어에서도 제한된 API를 더 쉽게 사용할 수 있습니다.

  • 제한된 API를 확장하기 전에 공개 논의를 시작하십시오

  • 제한된 API는 현재 지원되는 플랫폼의 기능만이 아니라 표준 C를 준수해야 합니다. 정확한 C 방언은 PEP 7에 설명되어 있습니다.

    • 문서 예제(더 일반적으로는 API의 의도된 사용법)도 표준 C를 준수해야 합니다.

    • 특히 함수 포인터를 void*(데이터 포인터)로 캐스팅하거나 그 반대로 캐스팅하지 마십시오.

  • 사용자가 쉽게 사용할 수 있는지 고려하십시오.

    • C에서는 사용 편의성 자체가 그다지 중요하지 않으며, 유용한 것은 API를 사용하는 데 필요한 상용구 코드를 줄이는 것입니다. 버그는 상용구 코드에 숨어들기 쉽습니다.

    • 함수가 특정 인자 값으로 자주 호출될 예정이라면, 그 값을 기본값으로 설정하는 것을 고려하십시오(NULL이 전달될 때 사용됩니다).

    • Limited API는 문서화가 잘되어 있어야 합니다.

  • 향후 확장을 고려하십시오

    • 향후 Python 버전에서 구조체에 새 필드를 추가해야 할 가능성이 있다면, 그렇게 할 수 있도록 하십시오.

    • 향후 CPython 버전에서 변경되거나 C API 구현마다 다를 수 있는 구현 세부 사항에 대해서는 가능한 한 적게 가정하십시오. 가장 중요한 CPython 고유의 구현 세부 사항은 다음과 관련됩니다:

      • GIL

      • 가비지 수집

      • PyObject, 리스트/튜플 및 기타 구조체의 메모리 레이아웃

이 지침을 따르면 성능이 저하되는 경우, 비제한 API에는 빠른 함수(또는 매크로)를 추가하고 Limited API에는 이에 상응하는 안정적인 항목을 추가하십시오.

불분명한 점이 있거나 지침을 어겨야 할 타당한 이유가 있다면, Discourse의 C API category에서 변경 사항을 논의하는 것을 고려하십시오.

Limited API에 새 정의 추가하기

  • Include/ 바로 아래의 헤더 파일에 다음과 같이 보호된 블록 안에 선언을 추가하십시오:

    #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03yy0000
    

    yy는 대상 CPython 버전에 해당하며, 예를 들어 Python 3.10의 경우 0x030A0000입니다.

  • Stable ABI 매니페스트인 Misc/stable_abi.toml에 항목을 추가하십시오

  • make regen-limited-abi를 사용하여 자동 생성 파일을 다시 생성하십시오. make가 없는 플랫폼에서는 다음 명령을 직접 실행하십시오:

    ./python ./Tools/build/stable_abi.py --generate-all ./Misc/stable_abi.toml
    
  • Python을 빌드하고 make check-limited-abi를 사용하여 검사하십시오. make가 없는 플랫폼에서는 다음 명령을 직접 실행하십시오:

    ./python ./Tools/build/stable_abi.py --all ./Misc/stable_abi.toml
    
  • 테스트를 추가하십시오 – 아래를 참조하십시오.

Limited API 테스트

Limited API는 C API의 하위 집합이므로 개별 함수의 동작을 테스트할 필요가 없습니다. 대신 테스트에서는 노출된 하위 집합을 사용하여 어떤 작업을 수행할 수 있는지 확인하거나, 현재 Limited API에서는 제거되었지만 이전 Limited API/Stable ABI 버전에서는 계속 지원해야 하는 기능을 실행해 볼 수 있습니다.

테스트 파일을 추가하려면:

  • Modules/_testcapi/yourfeature_limited.c C 파일을 추가하십시오. 해당 파일이 이미 있지만 Py_LIMITED_API 버전이 너무 낮다면, Python 3.12+용 yourfeature_limited_3_12.c처럼 버전 접미사를 추가하십시오.

  • #define Py_LIMITED_API를 필요한 최소 Limited API 버전으로 정의하십시오.

  • Py_LIMITED_API 정의 뒤에 #include "parts.h"를 추가하십시오

  • 호환되지 않는 빌드에서 건너뛰도록 파일의 나머지 전체를 #ifdef LIMITED_API_AVAILABLE로 감싸십시오.

  • C API tests에 대한 일반 지침을 따르십시오. 모든 추가 사항은 #ifdef LIMITED_API_AVAILABLE로 보호되는 섹션에 배치하십시오.

Lib/test의 Python 테스트에는 test.support.requires_limited_api 데코레이터를 사용하여 호환되지 않는 빌드에서 건너뛰도록 하십시오.