PEP 620 – C API에서 구현 세부 사항 숨기기
- Author:
- Victor Stinner <vstinner at python.org>
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 19-Jun-2020
- Python-Version:
- 3.12
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
구현 세부 사항을 숨기기 위해 C API와 호환되지 않는 변경 사항을 도입합니다.
대부분의 구현 세부 사항이 숨겨지면 CPython 내부의 발전은 C API 하위 호환성 문제에 의해 덜 제한될 것입니다. 새로운 기능을 추가하기가 훨씬 쉬워집니다.
태그가 지정된 포인터와 같이 단순한 마이크로 최적화를 넘어 더 고급 최적화를 CPython에서 실험할 수 있게 됩니다.
손상된 C 확장 기능의 수를 줄이는 프로세스를 정의합니다.
이 PEP의 구현은 여러 Python 버전에 걸쳐 신중하게 수행될 것으로 예상됩니다. 이 작업은 Python 3.7에서 이미 시작되었으며 대부분의 변경 사항은 이미 완료되었습니다. Process to reduce the number of broken C extensions는 진행 속도를 결정합니다.
PEP 철회
이 PEP는 범위가 너무 넓고 작업이 여러 Python 버전에 분산되어 전체 PEP에 대한 결정을 내리기 어렵다는 이유로 작성자가 철회했습니다. 이 PEP는 PEP 670과 같이 더 좁고 명확하게 정의된 범위를 가진 새로운 PEP들로 분할되었습니다.
동기
C API가 CPython의 발전을 가로막습니다.
C 구조체의 멤버를 추가하거나 제거하면 여러 하위 호환성 문제가 발생합니다.
새 멤버를 추가하면 안정적인 ABI (PEP 384)가 손상되며, 정적으로 선언된 타입의 경우 특히 그렇습니다(예: static PyTypeObject MyType = {...};). Python 3.4에서 PEP 442 “안전한 객체 종료”는 PyTypeObject 구조체의 끝에 tp_finalize 멤버를 추가했습니다. ABI 하위 호환성을 위해 타입 구조체에 tp_finalize 멤버가 포함되어 있는지를 알리는 새 Py_TPFLAGS_HAVE_FINALIZE 타입 플래그가 필요했습니다. 이 플래그는 Python 3.8에서 제거되었습니다(bpo-32388).
2009년에 출시된 Python 3.0부터 사용 중단된 PyTypeObject.tp_print 멤버가 Python 3.8 개발 주기에서 제거되었습니다. 그러나 이 변경으로 인해 너무 많은 C 확장 기능이 손상되어 3.8 최종 릴리스 전에 되돌려야 했습니다. 결국 이 멤버는 Python 3.9에서 다시 제거되었습니다.
C 확장 기능은 C API를 통해 간접적으로, 또는 직접적으로 구조체 멤버에 액세스할 수 있는 능력에 의존합니다. PyListObject와 같은 구조체의 수정은 고려조차 할 수 없습니다.
PyTypeObject 구조체가 가장 많이 발전한 것은 단순히 이를 수정하는 것 외에는 CPython을 발전시킬 다른 방법이 없었기 때문입니다.
C 확장 기능은 기술적으로 PyObject* 포인터의 역참조를 수행하여 PyObject 멤버에 액세스할 수 있습니다. 이 때문에 유효한 PyObject 구조체를 가리키지 않는 PyObject*에 작은 값을 저장하는 태그가 지정된 포인터와 같은 실험이 불가능합니다.
Python 가비지 컬렉터를 추적 가비지 컬렉터로 교체하려면 PyObject.ob_refcnt 참조 카운터도 제거해야 하는 반면, 현재 Py_INCREF() 및 Py_DECREF() 매크로는 PyObject.ob_refcnt에 직접 액세스합니다.
1990년 이래 동일한 CPython 설계: 구조체와 참조 카운팅
CPython 프로젝트가 만들어졌을 때는 단일 개발자가 유지 관리할 수 있을 만큼 구현을 단순하게 유지한다는 한 가지 원칙에 따라 작성했습니다. CPython의 복잡성은 크게 증가했고 많은 마이크로 최적화가 구현되었지만, CPython의 핵심 설계는 변경되지 않았습니다.
PyObject 및 PyTupleObject 구조체의 멤버는 1990년의 “Initial revision” 커밋 이후 변경되지 않았습니다.:
#define OB_HEAD \
unsigned int ob_refcnt; \
struct _typeobject *ob_type;
typedef struct _object {
OB_HEAD
} object;
typedef struct {
OB_VARHEAD
object *ob_item[1];
} tupleobject;
이름만 변경되었습니다. object는 PyObject로 이름이 변경되었고, tupleobject는 PyTupleObject로 이름이 변경되었습니다.
CPython은 여전히 내부적으로 참조 카운팅을 사용하여 Python 객체의 수명을 추적하며, Python C API를 통해 서드 파티 C 확장에도 이를 적용합니다.
모든 Python 객체는 힙에 할당되어야 하며 이동할 수 없습니다.
PyPy가 CPython보다 효율적인 이유는 무엇입니까?
PyPy 프로젝트는 평균적으로 CPython보다 4.2배 빠른 Python 구현입니다. PyPy 개발자들은 CPython을 포크하지 않고, 최적화 선택의 측면에서 더 큰 자유를 얻기 위해 처음부터 시작하기로 했습니다.
PyPy는 참조 카운팅을 사용하지 않고, 객체를 이동하는 추적 가비지 컬렉터를 사용합니다. 객체는 항상 힙에 할당하는 대신 스택에 할당할 수 있으며(또는 아예 할당하지 않을 수도 있습니다).
객체 레이아웃은 성능을 고려하여 설계되었습니다. 예를 들어, 리스트 전략은 정수를 객체가 아니라 정수로 직접 저장합니다.
또한 PyPy에는 효율적인 PyPy 설계 덕분에 빠른 코드를 생성하는 JIT 컴파일러도 있습니다.
PyPy의 병목 현상: Python C API
PyPy는 순수 Python 코드를 실행할 때 CPython보다 훨씬 효율적이지만, C 확장을 실행할 때는 CPython만큼 효율적이거나 더 느립니다.
C API는 PyObject*를 요구하고 구조체 멤버에 직접 접근할 수 있도록 하므로, PyPy는 CPython 객체를 PyPy 객체에 연결하고 둘 모두의 일관성을 유지해야 합니다. PyPy 객체를 CPython 객체로 변환하는 작업은 비효율적입니다. 또한 PyPy의 추적 가비지 컬렉터 위에 참조 카운팅도 구현해야 합니다.
Python C API가 CPython 구현에 지나치게 밀접하게 결합되어 있어 고수준 추상화가 없기 때문에 이러한 변환이 필요합니다. 예를 들어, 구조체 멤버는 공개 C API의 일부이므로 C 확장이 PyTupleObject.ob_item[0]를 직접 가져오거나 설정하는 것을 막을 수 있는 것은 아무것도 없습니다(튜플의 첫 번째 항목).
자세한 내용은 Antonio Cuni가 2018년 9월에 작성한 Inside cpyext: Why emulating CPython C API is so Hard 를 참조하십시오.
근거
구현 세부 정보 숨기기
C API에서 구현 세부 정보를 숨기면 여러 가지 장점이 있습니다.
- CPython에서 단순한 마이크로 최적화를 넘어 더 고급 최적화를 실험할 수 있게 됩니다. 예를 들어, 태그가 지정된 포인터를 사용하고 객체를 이동할 수 있는 추적 가비지 컬렉터로 가비지 컬렉터를 교체할 수 있습니다.
- CPython에 새로운 기능을 추가하기가 더 쉬워집니다.
- PyPy는 더 많은 경우에 CPython 객체로의 변환을 피하고 효율적인 PyPy 객체를 유지할 수 있게 됩니다.
- 새로운 Python 구현을 위한 C API를 구현하기가 더 쉬워집니다.
- 더 많은 C 확장이 CPython 이외의 Python 구현과 호환될 것입니다.
제한된 C API와의 관계
Python 3.4에서는 PEP 384 “Defining a Stable ABI”가 구현되었습니다. 이는 C API의 일부인 “제한된 C API”를 도입합니다. 제한된 C API를 사용하면 C 확장을 한 번만 빌드하여 여러 Python 버전에서 사용할 수 있습니다. 이것이 안정적인 ABI입니다.
주요 제한 사항은 PEP 384에서 C 확장 기능이 제한된 C API를 선택해야 한다는 것입니다. 이러한 선택을 한 프로젝트는 매우 적으며, 일반적으로 특히 Windows에서 바이너리 배포를 용이하게 하기 위해 선택합니다.
이 PEP는 C API를 제한된 C API 방향으로 이동합니다.
이상적으로는 C API가 제한된 C API가 되고 모든 C 확장이 안정적인 ABI를 사용하게 되겠지만, 이는 이 PEP의 범위를 벗어납니다.
사양
요약
- (Completed) C API 헤더 파일을 재구성합니다.
Include/cpython/및Include/internal/하위 디렉터리를 생성합니다. - (Completed) 구현 세부 사항을 노출하는 비공개 함수를 내부 C API로 이동합니다.
- (Completed) 매크로를 정적 인라인 함수로 변환합니다.
- (Completed)
Py_SET_TYPE(),Py_SET_REFCNT()및Py_SET_SIZE()새 함수를 추가합니다.Py_TYPE(),Py_REFCNT()및Py_SIZE()매크로는 l-value로 사용할 수 없는 함수가 됩니다. - (Completed) 새로운 C API 함수는 빌린 참조를 반환해서는 안 됩니다.
- (In Progress)
pythoncapi_compat.h헤더 파일을 제공합니다. - (In Progress) 구조체를 불투명하게 만들고 getter 및 setter 함수를 추가합니다.
- (Not Started)
PySequence_Fast_ITEMS()를 사용 중단합니다. - (Not Started)
PyTuple_GET_ITEM()및PyList_GET_ITEM()매크로를 정적 인라인 함수로 변환합니다.
C API 헤더 파일 재구성
C API의 첫 번째 사용자는 Python 자체였습니다. Python 외부에서 사용해서는 안 되는 API와 의도적으로 공개된 API 사이에는 명확한 구분이 없습니다.
헤더 파일은 3개의 API로 재구성되어야 합니다.
Include/디렉터리는 제한된 C API입니다. 구현 세부 사항이 없으며 구조체는 불투명합니다. 이를 사용하는 C 확장은 안정적인 ABI를 얻습니다.Include/cpython/디렉터리는 CPython C API입니다. “이식 가능한” API가 아니며 Python 버전에 더 많이 의존하고 일부 구현 세부 사항을 노출하며 호환되지 않는 변경이 일부 발생할 수 있습니다.Include/internal/디렉터리는 내부 C API입니다. 구현 세부 사항이며 Python이 릴리스될 때마다 호환되지 않는 변경이 발생할 가능성이 높습니다.
Include/cpython/ 디렉터리의 생성은 완전히 하위 호환됩니다. Include/cpython/ 헤더 파일은 직접 포함할 수 없으며, Py_LIMITED_API 매크로가 정의되지 않은 경우 Include/ 헤더 파일에 의해 자동으로 포함됩니다.
내부 C API는 설치되며, 코드를 실행하지 않고 구조체 멤버에 액세스해야 하는 디버거 및 프로파일러와 같은 특정 용도로 사용할 수 있습니다. 내부 C API를 사용하는 C 확장은 특정 Python 버전에 긴밀하게 결합되므로 Python 버전이 변경될 때마다 다시 컴파일해야 합니다.
STATUS: 완료됨 (Python 3.8에서)
헤더 파일 재구성은 Python 3.7에서 시작되었으며 Python 3.8에서 완료되었습니다.
비공개 함수를 내부 C API로 이동합니다.
구현 세부 사항을 노출하는 비공개 함수는 내부 C API로 이동해야 합니다.
C 확장이 CPython 구현 세부 사항을 노출하는 CPython 비공개 함수에 의존하는 경우, 다른 Python 구현은 이 C 확장을 지원하기 위해 해당 비공개 함수를 다시 구현해야 합니다.
상태: 완료되었습니다(Python 3.9에서).
Python 3.8에서 내부 C API로 이동된 비공개 함수입니다.
_PyObject_GC_TRACK(),_PyObject_GC_UNTRACK()
Python 3.9에서 제한된 C API에서 제외된 매크로 및 함수입니다.
_PyObject_SIZE(),_PyObject_VAR_SIZE()PyThreadState_DeleteCurrent()PyFPE_START_PROTECT(),PyFPE_END_PROTECT()_Py_NewReference(),_Py_ForgetReference()_PyTraceMalloc_NewReference()_Py_GetRefTotal()
Python 3.9에서 내부 C API로 이동된 비공개 함수입니다.
_Py_AS_GC(),_PyObject_GC_IS_TRACKED()및_PyGCHead_NEXT()와 같은 GC 함수입니다._Py_AddToAllObjects()(내보내지 않음)_PyDebug_PrintTotalRefs(),_Py_PrintReferences(),_Py_PrintReferenceAddresses()(내보내지 않음)
공개 “프리 리스트 지우기” 함수를 내부 C API로 이동하고 Python 3.9에서 비공개 함수로 이름을 변경합니다.
PyAsyncGen_ClearFreeLists()PyContext_ClearFreeList()PyDict_ClearFreeList()PyFloat_ClearFreeList()PyFrame_ClearFreeList()PyList_ClearFreeList()PyTuple_ClearFreeList()- 단순히 제거된 함수입니다.
PyMethod_ClearFreeList()및PyCFunction_ClearFreeList(): Python 3.9에서 바인드 메서드 프리 리스트가 제거되었습니다.PySet_ClearFreeList(): Python 3.4에서 집합 프리 리스트가 제거되었습니다.PyUnicode_ClearFreeList(): Python 3.3에서 유니코드 프리 리스트가 제거되었습니다.
매크로를 정적 인라인 함수로 변환하십시오.
매크로를 정적 인라인 함수로 변환하면 여러 가지 이점이 있습니다:
- 함수에는 명확하게 정의된 매개변수 타입과 반환 타입이 있습니다.
- 함수는 범위가 명확하게 정의된 변수(함수)를 사용할 수 있습니다.
- 디버거에서 함수에 중단점을 설정할 수 있고 프로파일러는 호출 스택에 함수 이름을 표시할 수 있습니다. 대부분의 경우 정적 인라인 함수가 인라인화된 경우에도 작동합니다.
- 함수에는 macros pitfalls이 없습니다.
매크로를 정적 인라인 함수로 변환해도 비정상적인 방식으로 매크로를 사용하는 극소수의 C 확장에만 영향을 주어야 합니다.
하위 호환성을 위해 함수는 PyObject*뿐만 아니라 모든 타입을 계속 허용해야 합니다. 대부분의 매크로가 매개변수를 PyObject*로 캐스팅하므로 컴파일러 경고를 방지해야 하기 때문입니다.
Python 3.6에서는 C 컴파일러가 정적 인라인 함수를 지원해야 합니다. PEP 7에서는 C99의 일부를 요구합니다.
상태: 완료됨(Python 3.9에서)
Python 3.8에서 정적 인라인 함수로 변환된 매크로:
Py_INCREF(),Py_DECREF()Py_XINCREF(),Py_XDECREF()PyObject_INIT(),PyObject_INIT_VAR()_PyObject_GC_TRACK(),_PyObject_GC_UNTRACK(),_Py_Dealloc()
Python 3.9에서 일반 함수로 변환된 매크로:
Py_EnterRecursiveCall(),Py_LeaveRecursiveCall()(제한된 C API에 추가됨)PyObject_INIT(),PyObject_INIT_VAR()PyObject_GET_WEAKREFS_LISTPTR()PyObject_CheckBuffer()PyIndex_Check()PyObject_IS_GC()PyObject_NEW()(PyObject_New()의 별칭),PyObject_NEW_VAR()(PyObject_NewVar()의 별칭)PyType_HasFeature()(항상PyType_GetFlags()를 호출해야 합니다)Py_TRASHCAN_BEGIN_CONDITION()및Py_TRASHCAN_END()매크로는 이제PyThreadState구조체의 멤버에 직접 액세스하는 대신 구현 세부 정보를 숨기는 함수를 호출합니다.
구조체를 불투명하게 만들기
다음 C API 구조체가 불투명해집니다:
PyInterpreterStatePyThreadStatePyGC_HeadPyTypeObjectPyObject및PyVarObjectPyTypeObjectPyObject또는PyVarObject를 상속하는 모든 타입
C 확장은 구조체 멤버를 가져오거나 설정할 때 getter 또는 setter 함수를 사용해야 합니다. 예를 들어, tuple->ob_item[0]은 PyTuple_GET_ITEM(tuple, 0)으로 대체해야 합니다.
참조 카운팅에서 벗어날 수 있으려면 PyObject는 불투명해져야 합니다. 현재 참조 카운터 PyObject.ob_refcnt는 C API에 노출되어 있습니다. 모든 구조체는 PyObject를 “상속”하므로 불투명해져야 합니다. 예를 들어, PyFloatObject는 PyObject를 상속합니다.:
typedef struct {
PyObject ob_base;
double ob_fval;
} PyFloatObject;
PyObject를 완전히 불투명하게 만들려면 Py_INCREF() 및 Py_DECREF()매크로를 함수 호출로 변환해야 합니다. 이 변경은 성능에 영향을 줍니다. 구조체를 불투명하게 만들 때 가장 마지막에 이루어지는 변경 중 하나가 될 가능성이 높습니다.
PyTypeObject 구조체를 불투명하게 만들면 타입을 정적으로 선언하는 C 확장이 작동하지 않게 됩니다(예: static PyTypeObject MyType = {...};). 대신 C 확장은 힙에 타입을 할당하기 위해 PyType_FromSpec()을 사용해야 합니다. 힙 타입을 사용하면 서브인터프리터와 호환되는 것과 같은 다른 장점도 있습니다. 이를 PEP 489 “다단계 확장 모듈 초기화”와 결합하면 C 확장의 동작이 Python 모듈에 더 가까워져, 둘 이상의 모듈 인스턴스를 생성할 수 있게 됩니다.
PyThreadState 구조체를 불투명하게 만들려면 C 확장에서 사용하는 멤버에 대한 getter 및 setter 함수를 추가해야 합니다.
상태: 진행 중(Python 3.8에서 시작됨)
PyInterpreterState 구조체는 Python 3.8에서 불투명해졌고(bpo-35886), PyGC_Head 구조체는 Python 3.9에서 불투명해졌습니다(bpo-40241).
다음 구조체를 불투명하게 만들기 위해 C API를 준비하는 작업을 추적하는 이슈입니다.
PyObject: bpo-39573PyTypeObject: bpo-40170PyFrameObject: bpo-40421- Python 3.9에는
PyFrame_GetCode()및PyFrame_GetBack()getter 함수가 추가되며,PyFrame_GetLineNumber는 제한된 C API로 이동합니다.
- Python 3.9에는
PyThreadState: bpo-39947- Python 3.9에는 3개의 getter 함수인
PyThreadState_GetFrame(),PyThreadState_GetID(),PyThreadState_GetInterpreter()가 추가됩니다.
- Python 3.9에는 3개의 getter 함수인
Py_TYPE()을 l-value로 사용하는 것을 허용하지 않음
Py_TYPE() 함수는 객체 타입인 해당 객체의 PyObject.ob_type 멤버를 가져옵니다. 이는 타입을 설정하기 위해 l-value로 사용할 수 있는 매크로로 구현되어 있습니다: Py_TYPE(obj) = new_type. 이 코드는 PyObject.ob_type을 직접 수정할 수 있다는 가정에 의존합니다. 이로 인해 PyObject 구조체를 불투명하게 만들 수 없습니다.
새로운 setter 함수 Py_SET_TYPE(), Py_SET_REFCNT() 및 Py_SET_SIZE()가 추가되었으며, 대신 이를 사용해야 합니다.
Py_TYPE(), Py_REFCNT() 및 Py_SIZE()매크로는 l-value로 사용할 수 없는 정적 인라인 함수로 변환해야 합니다.
예를 들어, Py_TYPE() 매크로는:
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
다음과 같이 변경됩니다:
#define _PyObject_CAST_CONST(op) ((const PyObject*)(op))
static inline PyTypeObject* _Py_TYPE(const PyObject *ob) {
return ob->ob_type;
}
#define Py_TYPE(ob) _Py_TYPE(_PyObject_CAST_CONST(ob))
STATUS: 완료됨 (Python 3.10에서)
새로운 함수 Py_SET_TYPE(), Py_SET_REFCNT() 및 Py_SET_SIZE()가 Python 3.9에 추가되었습니다.
Python 3.10에서는 Py_TYPE(), Py_REFCNT() 및 Py_SIZE()를 더 이상 l-value로 사용할 수 없으며, 대신 새로운 setter 함수를 사용해야 합니다.
새로운 C API 함수는 빌린 참조를 반환해서는 안 됩니다
함수가 빌린 참조를 반환하면 호출자가 이 참조의 사용을 중단하는 시점을 Python이 추적할 수 없습니다.
예를 들어, Python의 list타입이 작은 정수에 특화되어 Python 객체 대신 “raw” 숫자를 직접 저장한다면, PyList_GetItem()은 임시 Python 객체를 생성해야 합니다. 문제는 임시 객체를 삭제해도 안전한 시점을 결정하는 것입니다.
일반적인 지침은 새로운 C API 함수에서 빌린 참조를 반환하지 않는 것입니다.
이 PEP에서는 빌린 참조를 반환하는 함수를 제거할 예정이 없습니다.
STATUS: 완료됨 (Python 3.9에서)
Python 3.9에서는 Python 객체를 반환하는 새로운 C API 함수가 강한 참조만 반환합니다:
PyFrame_GetBack()PyFrame_GetCode()PyObject_CallNoArgs()PyObject_CallOneArg()PyThreadState_GetFrame()
PyObject**를 반환하는 함수를 피하십시오
PySequence_Fast_ITEMS() 함수는 PyObject* 객체 배열에 직접 접근할 수 있도록 합니다. 이 함수는 PyTuple_GetItem() 및 PyList_GetItem()을 대신 사용하도록 더 이상 사용되지 않습니다.
PyTuple_GET_ITEM()은 PyTupleObject.ob_item 멤버에 직접 접근하는 데 악용될 수 있습니다:
PyObject **items = &PyTuple_GET_ITEM(0);
이를 방지하기 위해 PyTuple_GET_ITEM() 및 PyList_GET_ITEM() 매크로는 static inline 함수로 변환됩니다.
STATUS: 시작되지 않음
새로운 pythoncapi_compat.h 헤더 파일
구조체를 불투명하게 만들려면 C 확장 기능을 수정하여 getter 및 setter 함수를 사용해야 합니다. 실질적인 문제는 이러한 함수를 제공하지 않는 이전 Python 버전에 대한 지원을 유지하는 방법입니다.
예를 들어, Python 3.10에서는 더 이상 Py_TYPE()을 l-value로 사용할 수 없습니다. 대신 새로운 Py_SET_TYPE() 함수를 사용해야 합니다:
#if PY_VERSION_HEX >= 0x030900A4
Py_SET_TYPE(&MyType, &PyType_Type);
#else
Py_TYPE(&MyType) = &PyType_Type;
#endif
이 코드는 Python 코드 베이스를 Python 2에서 Python 3으로 이식한 개발자에게 익숙하게 들릴 수 있습니다.
Python은 이전 Python 버전에 새로운 C API 함수를 제공하는 새로운 pythoncapi_compat.h 헤더 파일을 배포합니다. 예시:
#if PY_VERSION_HEX < 0x030900A4
static inline void
_Py_SET_TYPE(PyObject *ob, PyTypeObject *type)
{
ob->ob_type = type;
}
#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type)
#endif // PY_VERSION_HEX < 0x030900A4
이 헤더 파일을 사용하면 Py_SET_TYPE()를 이전 Python 버전에서도 사용할 수 있습니다.
개발자는 자신의 프로젝트에 이 파일을 복사하거나, C 확장에 필요한 몇 가지 함수만 복사하여 붙여넣을 수도 있습니다.
상태: 진행 중 (구현되었지만 아직 CPython에서 배포되지 않음)
pythoncapi_compat.h 헤더 파일은 현재 다음 위치에서 개발되고 있습니다: https://github.com/pythoncapi/pythoncapi_compat
손상된 C 확장의 수를 줄이기 위한 절차
이 PEP에 열거된 C API 비호환 변경 사항을 도입할 때 손상된 C 확장의 수를 줄이기 위한 절차:
- 비호환 변경 사항의 영향을 받는 인기 있는 C 확장이 얼마나 많은지 추정하십시오.
- 손상된 C 확장의 유지 관리자와 협력하여 향후 비호환 변경 사항에 대비하도록 해당 코드를 준비하십시오.
- Python에 비호환 변경 사항을 도입하십시오. 문서에서는 기존 코드를 이식하는 방법을 설명해야 합니다. 테스트에 더 많은 시간을 할애할 수 있도록 이러한 변경 사항을 개발 주기 초기에 병합하는 것이 좋습니다.
- 많은 C 확장을 손상시킬 가능성이 가장 높은 변경 사항은 capi-sig 메일링 리스트에 공지하여 C 확장 유지 관리자에게 알리고, 다음 Python을 위한 프로젝트를 준비하도록 해야 합니다.
- 변경 사항으로 인해 너무 많은 프로젝트가 손상되는 경우에는 손상된 패키지의 수와 해당 패키지의 Python 커뮤니티 내 중요성, 그리고 변경 사항 자체의 중요성을 고려하여 변경 사항을 되돌리는 방안을 논의해야 합니다.
조정은 일반적으로 프로젝트에 이슈를 보고하거나 변경 사항을 제안하는 것을 의미합니다. 손상된 모든 프로젝트에 대한 수정 사항이 포함된 새 릴리스를 기다릴 필요는 없습니다.
점점 더 많은 C 확장이 C API를 직접 사용하는 대신 Cython을 사용하여 작성되고 있으므로, 호환되지 않는 변경 사항에 대해 Cython이 미리 대비되어 있도록 하는 것이 중요합니다. 이는 (Cython이 생성한 코드를 배포하는 C 확장의 경우) C 확장 관리자가 업데이트된 Cython으로 생성된 코드를 담은 새 버전을 배포할 시간을 더 많이 확보해 줍니다.
향후의 호환되지 않는 변경 사항은 문서에서 함수를 사용 중단(deprecate) 처리하고 Py_DEPRECATED()로 함수에 주석을 다는 방식으로 예고할 수 있습니다. 그러나 구조체를 불투명하게 만드는 것과 매크로를 l-value로 사용하지 못하게 막는 것은 Py_DEPRECATED()로 사용 중단 처리를 할 수 없습니다.
중요한 부분은 조율이며, CPython의 발전과 하위 호환성 사이의 균형을 찾는 것입니다. 예를 들어, PyPI에 있는 임의의 오래되고 잘 알려지지 않았으며 관리되지 않는 C 확장을 망가뜨리는 것은 numpy를 망가뜨리는 것보다 심각도가 낮습니다.
변경 사항이 되돌려지면, 그 변경 사항을 더 잘 준비하기 위해 조율 단계로 돌아갑니다. 더 많은 C 확장이 준비되면, 호환되지 않는 변경 사항을 다시 고려할 수 있습니다.
버전 이력
- 버전 3, 2020년 6월: PEP를 처음부터 다시 작성했습니다. 이제 Python은 새로운
pythoncapi_compat.h헤더를 배포하며, 이 PEP에 나열된 C API 호환되지 않는 변경 사항을 도입할 때 손상되는 C 확장의 수를 줄이기 위한 절차가 정의되어 있습니다. - 버전 2, 2020년 4월: PEP: 구현 세부 사항을 숨기도록 C API 수정하기.
- 버전 1, 2017년 7월: PEP: C API에서 구현 세부 사항 숨기기를 python-ideas에 보냈습니다.
Copyright
This document has been placed in the public domain.