Python의 C API 변경하기¶
C API는 다음과 같은 계층으로 나뉩니다:
Py_BUILD_CORE가 정의되어 있을 때 사용할 수 있는 내부 비공개 API입니다.Include/internal/에 선언하는 것이 이상적입니다. 이름이 밑줄로 시작하는 모든 API도 비공개로 간주합니다.PyUnstable_이름 접두사로 식별되는 불안정 C API입니다. 일반 공개 API와 함께 Include/cpython/에 선언하는 것이 이상적입니다.Include/Python.h를 일반적인 방식으로 포함할 때 사용할 수 있는 “일반” 공개 C API입니다.
Include/cpython/에 선언하는 것이 이상적입니다.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_NewRefmacro와 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 데코레이터를 사용하여, 호환되지 않는 빌드에서는 테스트를 건너뛰도록 하십시오.