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 and ABI 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 카테고리나 이슈에서 논의해야 합니다.

PyAPI_FUNC 또는 PyAPI_DATA를 사용하는 경우

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

extern 키워드를 사용하는 경우

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

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

비공개 이름

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

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

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

  • 새 API가 실제로 어떻게 쓰이는지 시험하기 위해 Python 릴리스에 포함되는 “잠정적(provisional)” API

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

내부 API 테스트

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

공개 C API

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

공개 C API는 Include/cpython/에 정의해야 합니다(제한된 API에 속하는 경우는 예외이며, 아래를 참조하십시오).

새 공개 API를 추가하기 전에 C API 작업 그룹의 decisions repo에 문의하십시오. 그렇게 하면 새로 추가되는 API가 일관되고 유지 보수하기 쉽도록 하는 데 도움이 됩니다.

C99에 없는 C 기능을 요구하기 전에도 C API 작업 그룹에 확인하십시오. 공개 문서는 C11과의 호환성만 약속하지만, 실제로는 C11 기능을 필요할 때마다 하나씩만 도입합니다.

공개 API 확장/변경 지침

  • 새 API가 참조 카운팅 규칙을 따르는지 확인하십시오. (규칙을 따르면 API의 동작을 이해하기 쉬워지고, 다른 Python 구현에서도 사용하기 쉬워집니다.)

    • 함수는 참조를 훔쳐서는 안 됩니다

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

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

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

  • PyObject *를 반환하는 함수는 성공하면 유효한 포인터를 반환하고, 오류가 발생하면 예외를 발생시킨 상태에서 NULL을 반환해야 합니다. 그 밖의 대부분의 API는 오류가 발생하면 예외를 발생시킨 상태에서 -1을 반환하고, 성공하면 0을 반환해야 합니다.

  • 결과가 작은 결과와 큰 결과로 나뉘는 API는 작은 결과에 0을, 큰 결과에 1을 반환해야 합니다. 세 가지 값을 반환하는 조회 함수를 예로 들어 보겠습니다.

    • return -1: 내부 오류 또는 API 오용이며, 예외가 발생한 상태입니다

    • return 0: 조회에 성공했으며, 항목을 찾지 못했습니다

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

이 지침이 여러분의 API에 맞지 않는다면 공개 토론을 시작하십시오.

참고

반환 값이란 C return 문이 반환하는 값을 의미합니다.

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.c의 PyInit__testcapi에서 _PyTestCapi_Init_*를 호출하십시오.

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

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

기존 테스트를 옮길 때, 실제로 사용자 정의 예외를 테스트하는 경우가 아니라면 TestError를 PyExc_AssertionError로 바꾸어도 좋습니다.

불안정 C API

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

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

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

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

  • 의도치 않게 망가뜨리지 않도록 테스트가 필요합니다.

  • 우리와 사용자 모두가 기대되는 동작에 합의할 수 있도록 문서가 필요합니다.

  • 같은 방식으로 테스트되고 문서화됩니다.

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

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

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

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

“불안정”하다고는 하지만, 서드파티 코드가 이 API를 안정적으로 사용할 수 있도록 보장하는 규칙이 있습니다:

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

  • 기존 기능에 대한 새로운 불안정 API 추가는 베타 기능 동결 이후에도 첫 번째 릴리스 후보 전까지 허용됩니다. 베타 기간에는 Core Development Discourse에서 합의를 거쳐야 합니다.

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

  • API를 불안정 계층으로 옮기거나 불안정 계층에서 다른 계층으로 옮길 때, 이전 이름은 호환되지 않는 변경이 이루어질 때까지 (폐지 예정 상태로) 계속 사용할 수 있어야 합니다. 다시 말해, 호출하는 코드를 깨뜨리는 것은 허용되지만 불필요하게 깨뜨려서는 안 됩니다.

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

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

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

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

  • “What’s New”에 변경 사항을 공지하십시오.

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

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

  • 기본보다 덜 안정적이라고 문서화된 API.

  • 이름이 밑줄로 시작하는 API.

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

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

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

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

  • What’s New에 변경 사항을 공지하십시오.

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

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

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

  • What’s New에 변경 사항을 공지하십시오.

이전 이름은 새 공개 이름이 폐지 예정으로 표시되거나 제거될 때까지 계속 사용할 수 있어야 합니다. 이전 이름은 애초에 불안정한 API였으므로 폐지 예정으로 표시할 필요는 없지만, 어떤 함수가 이제 더 많은 사용자에게 제공될 준비가 되었다는 이유만으로 잘 동작하는 코드를 깨뜨릴 필요도 없습니다.

제한된 API

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

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

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

제한된 API 변경 및 항목 제거에 관한 지침

안정 ABI를 깨뜨려서는 안 되지만, 다음 조건을 충족하는 경우 기존 제한된 API를 변경하거나 항목을 제거할 수 있습니다:

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

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

이는 까다로운 작업이며 신중한 검토가 필요합니다. 몇 가지 예는 다음과 같습니다:

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

  • 구조체 구성원이 제한된 API의 어느 버전에든 포함된 적이 있다면 그 순서를 재배치할 수 없습니다.

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

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

안정 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이 전달될 때 사용됨)으로 만드는 것을 고려하십시오.

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

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

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

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

      • GIL

      • 가비지 수집

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

이 지침을 따르는 것이 성능을 저하시킨다면, 제한되지 않은 API에 빠른 함수(또는 매크로)를 추가하고 제한된 API에는 안정적인 대응물을 추가하십시오.

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

제한된 API에 새 정의 추가하기

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

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

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

  • 안정 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
    
  • 테스트를 추가하십시오 – 아래를 참조하십시오.

제한된 API 테스트

제한된 API는 C API의 부분 집합이므로, 개별 함수의 동작을 테스트할 필요는 없습니다. 그보다는, 노출된 부분 집합을 사용하여 어떤 작업이 가능한지 검증하거나, 현재 제한된 API에서는 제거되었지만 이전 제한된 API/안정 ABI 버전을 위해 여전히 지원되어야 하는 기능을 실행해 보는 테스트를 작성할 수 있습니다.

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

  • C 파일 Modules/_testcapi/yourfeature_limited.c를 추가하십시오. 해당 파일이 이미 있지만 그 파일의 Py_LIMITED_API 버전이 너무 낮다면, 버전 접미사를 붙이십시오. 예를 들어 Python 3.12 이상용이라면 yourfeature_limited_3_12.c입니다.

  • #define Py_LIMITED_API를 필요한 최소 제한된 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 데코레이터를 사용하여, 호환되지 않는 빌드에서는 테스트를 건너뛰도록 하십시오.