PEP 743 – Python C API에 Py_OMIT_LEGACY_API 추가
- Author:
- Victor Stinner <vstinner at python.org>, Petr Viktorin <encukou at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 11-Mar-2024
- Python-Version:
- 3.15
- Post-History:
- 11-Mar-2024, 27-Jul-2024
- Resolution:
- 20-Feb-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 공지
운영 위원회(Steering Council)는 현재 형태의 이 PEP를 거부했습니다:
이 PEP가 해결하려는 문제가 해결할 가치가 있다는 점에는 동의하지만, 이것이 올바른 접근 방식이라고 확신하지는 않습니다.
전체 게시글을 참조하십시오.
초록
폐지되었거나 소프트 폐지된 심볼을 숨기는 Py_OMIT_LEGACY_API C 매크로를 추가하여, 다른 API로 해결된 알려진 문제가 있는 API를 사용자가 선택적으로 사용하지 않을 수 있도록 합니다.
또한 Py_ 접두사가 없는 API에 대해 네임스페이스가 적용된 대안을 추가하고, 원래 이름은 소프트 폐지합니다.
동기
Python C API의 일부에는 나중에 돌이켜 보아야만 분명해지는 결함이 있습니다.
어떤 API가 기능이나 최적화의 추가를 가로막거나, 심각한 보안 위험 또는 유지 보수 부담을 초래한다면, PEP 387에 설명된 대로 해당 API를 폐지하고 제거할 수 있습니다.
그러나 이렇게 하더라도 “날카로운 모서리”가 있는 API가 일부 남게 됩니다. 이러한 API는 현재 사용자에게는 잘 동작하지만, 새 코드에서는 피해야 합니다. 예를 들면 다음과 같습니다:
- 예외를 알릴 수 없어서, 실패가 무시되거나 치명적 오류와 함께 프로세스가 종료되는 API입니다. 예를 들어
PyObject_HasAttr가 있습니다. - 스레드 안전하지 않은 API입니다. 예를 들어 가변 객체로부터 참조를 빌려 오거나, 완성되지 않은 가변 객체를 노출하는 경우입니다. 예를 들어
PyDict_GetItemWithError가 있습니다. - 이름에
Py/_Py접두사를 사용하지 않아 다른 코드와 충돌할 수 있는 API입니다. 예를 들어setter가 있습니다.
이러한 결함에도 불구하고 대개는 해당 API를 올바르게 사용할 수 있다는 점에 유의해야 합니다. 예를 들어 단일 스레드 환경에서는 스레드 안전성이 문제가 되지 않습니다. 일부 – 또는 심지어 대부분의 – 다른 맥락에서는 잘못되었을 API를 사용하더라도, 동작하는 코드를 망가뜨리고 싶지는 않습니다.
반면에, 특히 더 안전한 대안이 존재하는 경우에는 새 코드에서 사용자가 이러한 “바람직하지 않은” API를 쓰지 않도록 유도하고자 합니다.
Py 접두사 추가
CPython 헤더에 정의된 일부 이름에는 네임스페이스가 적용되어 있지 않습니다. 즉, Py 접두사(또는 그 변형인 _Py 및 대소문자를 달리한 형태)가 없습니다. 예를 들어, 단순히 setter라는 이름의 함수 타입을 선언하고 있습니다.
이러한 이름은 (make smelly로 검사하듯이) ABI로 내보내지지는 않지만, 사용자 코드와 충돌할 수 있으며, 더 중요하게는 서드파티 확장 모듈에 링크되는 라이브러리와 충돌할 수 있습니다.
네임스페이스가 적용된 별칭을 제공하고 이러한 이름을 (소프트) 폐지하는 것도 가능하지만, 서드파티 코드와 충돌하지 않게 하는 유일한 방법은 Python 헤더에서 이 이름들을 아예 정의하지 않는 것입니다.
근거
사용자가 원할 경우 “바람직하지 않은” API를 쉽게 피할 수 있는 방법을 제공하고자 합니다.
이 일은 서드파티 린터에 맡기는 것으로 충분할 수도 있습니다. 그러려면 (소프트) 폐기된 API 목록을 그러한 린터에 제공할 좋은 방법이 필요합니다. 그 방법을 추가하는 김에, 린터가 할 일을 CPython 헤더에서 직접 – 꽤 쉽게 – 처리할 수 있으며, 그러면 별도의 도구가 필요 없습니다. Python과 달리 C에서는 사용자가 “옵트인” 매크로를 정의하게 하는 방식으로, 사용 가능한 API를 – 프로젝트 전체 또는 개별 소스 파일 단위로 – 꽤 쉽게 제한할 수 있습니다.
이미 Py_LIMITED_API로 비슷한 일을 하고 있습니다. 이 매크로는 사용 가능한 API를 안정 ABI로 컴파일되는 부분 집합으로 제한합니다. (돌이켜 보면 그런 종류의 제한에는 다른 매크로 이름을 사용했어야 했지만, 이제 와서 바꾸기에는 너무 늦었습니다.)
분명히 말하자면, 이 메커니즘은 폐지를 대체하는 것이 아닙니다. 폐지는 새로운 기능이나 최적화를 가로막거나, 보안 위험 또는 유지보수 부담을 초래하는 API에 적용하는 것입니다. 반면에 이 메커니즘은 “조금 더 나은 방법을 찾은” 경우를 위한 것입니다 – 이를테면 잘못 사용하기 더 어렵거나, 단순히 오해를 덜 부르는 이름을 가진 방법 말입니다. (가벼운 이야기를 덧붙이자면, 많은 사람이 함수 사이의 빈 줄 수를 두고 자신에게 경고를 퍼붓도록 코드 품질 검사기를 설정합니다. 그들이 더 실질적인 “코드 스멜”을 찾아낼 수 있도록 도와줍시다!)
제안하는 매크로는 어떤 API 정의도 변경하지 않으며, 오직 숨기기만 합니다. 따라서 코드가 이 매크로를 정의한 상태로 컴파일된다면, 매크로 없이도 동일한 동작으로 컴파일됩니다. 이는 코어 개발자에게 영향을 미칩니다. 바람직하지 않은 동작을 처리하려면 새롭고 더 나은 API를 도입한 다음에 기존 API의 사용을 권장하지 않아야 합니다. 이는 다시, 같은 함수의 이름을 여러 번 바꾸는 일을 피할 수 있도록, 한 가지 종류의 문제를 코드베이스 전체에서 일괄 정리하기보다는 개별 API를 살펴보고 그 API의 알려진 문제를 한꺼번에 모두 고쳐야 한다는 것을 의미합니다.
명세
Py_OMIT_LEGACY_API 매크로를 도입합니다. 이 매크로가 #include <Python.h> 앞에서 정의되면, 일부 API 정의가 – 아래에서 설명하는 대로 – Python 헤더 파일에서 생략됩니다.
이 매크로는 <Python.h>에서 노출되는 완전한 최상위 정의만 생략합니다. 그 밖의 것들(ABI, 구조체 정의, 매크로 확장, static inline 함수 본문 등)은 영향을 받지 않습니다.
C API 워킹 그룹(PEP 731)이 생략되는 정의의 집합에 대한 권한을 가집니다.
생략되는 정의의 집합은 CPython의 특정 기능 릴리스에 묶이며, 각 3.x.0 베타 1 릴리스에서 확정됩니다. 드물게는 언제든지 항목이 제거(즉, 사용할 수 있게)될 수 있습니다.
생략되는 API에 대한 요구 사항
Py_OMIT_LEGACY_API로 생략되는 API는 다음 조건을 충족해야 합니다:
- 소프트 폐기되어야 합니다(PEP 387 참조);
- 해당 API의 알려진 모든 사용 사례에 대해 문서화된 대안이나 우회 방법이 있어야 합니다;
- 계속 동작함을 보장하는 테스트가 있어야 합니다(
#define또는typedef를 사용한 1:1 이름 변경은 예외입니다); - 문서화되어 있어야 합니다(이전 버전의 문서에서 한 번도 언급된 적이 없는 경우는 예외입니다); 그리고
- C API 워킹 그룹의 승인을 받아야 합니다. (워킹 그룹은 서로 관련된 API 묶음에 대해 일괄 승인을 할 수 있습니다. 예시는 아래의 초기 집합을 참조하십시오.)
Py_OMIT_LEGACY_API는 더 나은 대안으로 손쉽게 대체할 수 있는 API를 위한 것임에 유의하십시오. 대체할 수단이 없는 API는 일반적으로 대신 폐기되어야 합니다.
위치
Py_OMIT_LEGACY_API에 의해 생략되는 모든 API 정의는 새 헤더인 Include/legacy.h로 옮겨집니다.
이는 린터 작성자가 목록을 작성하는 데 도움을 주어, 해당 API를 오류가 아닌 경고로 플래그 지정할 수 있도록 하기 위한 것입니다.
소스 전용 구성 요소(매크로, 타입)의 단순한 이름 변경에 대해서는, 대체 항목을 추가하는 것과 같은 버전 – 또는 같은 PR – 에서 이름이 생략될 것으로 예상한다는 점에 유의하십시오. 즉, 원래 정의의 이름이 변경되고, 옛 이름에 대한 typedef 또는 #define이 Include/legacy.h에 추가됩니다.
문서
생략된 API에 대한 문서는 일반적으로 다음과 같아야 합니다:
- 권장되는 대체 항목 뒤에 나타나야 하고,
- 대체 항목을 참조해야 하며(예: “X와 비슷하지만…”),
- 대체 항목과의 차이점 및 마이그레이션 조언에 초점을 맞춰야 합니다.
타당한 이유가 있다면 예외도 가능합니다.
초기 집합
Py_OMIT_LEGACY_API가 설정되면 다음 API가 생략됩니다:
- 빌린 참조를 반환하는 API 생략:
생략되는 API 대체 항목 PyDict_GetItem()PyDict_GetItemRef()PyDict_GetItemString()PyDict_GetItemStringRef()PyImport_AddModule()PyImport_AddModuleRef()PyList_GetItem()PyList_GetItemRef() - 사용 중단된 API 생략:
생략되는 폐지 예정 API 대체 항목 PY_FORMAT_SIZE_T"z"PY_UNICODE_TYPEwchar_tPyCode_GetFirstFree()PyUnstable_Code_GetFirstFree()PyCode_New()PyUnstable_Code_New()PyCode_NewWithPosOnlyArgs()PyUnstable_Code_NewWithPosOnlyArgs()PyImport_ImportModuleNoBlock()PyImport_ImportModule()PyMem_DEL()PyMem_Free()PyMem_Del()PyMem_Free()PyMem_FREE()PyMem_Free()PyMem_MALLOC()PyMem_Malloc()PyMem_NEW()PyMem_New()PyMem_REALLOC()PyMem_Realloc()PyMem_RESIZE()PyMem_Resize()PyModule_GetFilename()PyModule_GetFilenameObject()PyOS_AfterFork()PyOS_AfterFork_Child()PyObject_DEL()PyObject_Free()PyObject_Del()PyObject_Free()PyObject_FREE()PyObject_Free()PyObject_MALLOC()PyObject_Malloc()PyObject_REALLOC()PyObject_Realloc()PySlice_GetIndicesEx()(두 번 호출합니다. 현재 문서를 참조하십시오.) PyThread_ReInitTLS()(더 이상 필요하지 않습니다.) PyThread_create_key()PyThread_tss_alloc()PyThread_delete_key()PyThread_tss_free()PyThread_delete_key_value()PyThread_tss_delete()PyThread_get_key_value()PyThread_tss_get()PyThread_set_key_value()PyThread_tss_set()PyUnicode_AsDecodedObject()PyUnicode_Decode()PyUnicode_AsDecodedUnicode()PyUnicode_Decode()PyUnicode_AsEncodedObject()PyUnicode_AsEncodedString()PyUnicode_AsEncodedUnicode()PyUnicode_AsEncodedString()PyUnicode_IS_READY()(더 이상 필요하지 않습니다.) PyUnicode_READY()(더 이상 필요하지 않습니다.) PyWeakref_GET_OBJECT()PyWeakref_GetRef()PyWeakref_GetObject()PyWeakref_GetRef()Py_UNICODEwchar_t_PyCode_GetExtra()PyUnstable_Code_GetExtra()_PyCode_SetExtra()PyUnstable_Code_SetExtra()_PyDict_GetItemStringWithError()PyDict_GetItemStringRef()_PyEval_RequestCodeExtraIndex()PyUnstable_Eval_RequestCodeExtraIndex()_PyHASH_BITSPyHASH_BITS_PyHASH_IMAGPyHASH_IMAG_PyHASH_INFPyHASH_INF_PyHASH_MODULUSPyHASH_MODULUS_PyHASH_MULTIPLIERPyHASH_MULTIPLIER_PyObject_EXTRA_INIT(더 이상 필요하지 않음) _PyThreadState_UncheckedGet()PyThreadState_GetUnchecked()_PyUnicode_AsString()PyUnicode_AsUTF8()_Py_HashPointer()Py_HashPointer()_Py_T_OBJECT( tp_getset; 문서는 추후 작성 예정)_Py_WRITE_RESTRICTED(더 이상 필요하지 않음) - 다음 API를 소프트 폐지(soft deprecate)하고 생략합니다:
생략되는 폐지 예정 API 대체 항목 PyDict_GetItemWithError()PyDict_GetItemRef()PyDict_SetDefault()PyDict_SetDefaultRef()PyMapping_HasKey()PyMapping_HasKeyWithError()PyMapping_HasKeyString()PyMapping_HasKeyStringWithError()PyObject_HasAttr()PyObject_HasAttrWithError()PyObject_HasAttrString()PyObject_HasAttrStringWithError() <structmember.h>레거시 API 생략:<Python.h>에 포함되지 않아 별도로 포함해야 하는 헤더 파일structmember.h는Py_OMIT_LEGACY_API가 정의되어 있으면#error를 발생시킵니다. 이는 다음 API에 영향을 줍니다:생략되는 폐지 예정 API 대체 항목 T_SHORTPy_T_SHORTT_INTPy_T_INTT_LONGPy_T_LONGT_FLOATPy_T_FLOATT_DOUBLEPy_T_DOUBLET_STRINGPy_T_STRINGT_OBJECT( tp_getset; 문서 작성 예정)T_CHARPy_T_CHART_BYTEPy_T_BYTET_UBYTEPy_T_UBYTET_USHORTPy_T_USHORTT_UINTPy_T_UINTT_ULONGPy_T_ULONGT_STRING_INPLACEPy_T_STRING_INPLACET_BOOLPy_T_BOOLT_OBJECT_EXPy_T_OBJECT_EXT_LONGLONGPy_T_LONGLONGT_ULONGLONGPy_T_ULONGLONGT_PYSSIZETPy_T_PYSSIZETT_NONE( tp_getset; 문서 작성 예정)READONLYPy_READONLYPY_AUDIT_READPy_AUDIT_READREAD_RESTRICTEDPy_AUDIT_READPY_WRITE_RESTRICTED(더 이상 필요하지 않음) RESTRICTEDPy_AUDIT_READ- 소프트 폐지된 매크로를 생략합니다:
생략되는 매크로 대체 항목 Py_IS_NAN()isnan()(C99+<math.h>)Py_IS_INFINITY()isinf(X)(C99+<math.h>)Py_IS_FINITE()isfinite(X)(C99+<math.h>)Py_MEMCPY()memcpy()(C<string.h>) Py/_Py접두사가 없는 typedef(getter,setter,allocfunc, …)를 소프트 폐지하고 생략하며, 대신 접두사를 붙인 새로운 typedef(Py_getter등)를 사용하도록 합니다.Py/_Py접두사가 없는 매크로(METH_O,CO_COROUTINE,FUTURE_ANNOTATIONS,WAIT_LOCK, …)를 소프트 폐지하고 생략하며, 대신 접두사를 붙인 새로운 매크로(Py_METH_O등)를 사용하도록 합니다.- C API 작업 그룹이 승인한 기타 항목
제안된 대체 항목이나 관련 문서 중 3.14.0b1까지 추가되지 못한 것이 있으면, 해당 항목은 이후 버전의 Py_OMIT_LEGACY_API에서 생략됩니다. (configure가 생성하는 매크로인 HAVE_*, WITH_*, ALIGNOF_*, SIZEOF_* 및 공통 접두사가 없는 몇몇 매크로가 이에 해당할 것으로 예상합니다.)
구현
미정
하위 호환성
이 매크로는 하위 호환성을 유지합니다. 개발자는 각자의 속도에 맞춰 이 매크로를 도입하고 갱신할 수 있으며, 필요하다면 한 번에 소스 파일 하나씩 적용할 수도 있습니다.
향후 CPython 버전에서 Py_OMIT_LEGACY_API가 숨기는 API 집합에 더 많은 API가 추가되어 사용자 코드가 깨질 수 있습니다. 이 경우 매크로 정의를 해제하거나(이는 안전합니다) 코드를 수정하면 됩니다.
논의
- PEP 743 – Python C API에 Py_COMPAT_API_VERSION 추가 (두 번째 시도) (2024년 7월)
- 대규모 이름 변경 마무리하기 (2024년 5월)
- PEP 743: Python C API에 Py_COMPAT_API_VERSION 추가 (2024년 3월)
- C API Evolutions: 폐지된 함수를 숨기는 매크로 (2023년 10월)
- C API Problems: Opt-in macro for a new clean API? Subset of functions with no known issues (2023년 6월)
선행 사례
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.