[Final] PEP 733 - An Evaluation of Python’s Public C API
원문 링크: PEP 733 - An Evaluation of Python’s Public C API
상태: Final 유형: Informational 작성일: 16-Oct-2023
PEP 733 – Python Public C API 평가
작성자: Erlend Egeberg Aasland 외 25명 상태: 최종 (Final) 유형: 정보 (Informational) 생성일: 2023년 10월 16일 게시 이력: 2023년 11월 1일
요약 (Abstract)
이 정보성 PEP는 Python의 Public C API에 대한 우리의 공통된 견해를 설명합니다. 이 문서는 다음을 정의합니다.
- C API의 목적
- 이해관계자(stakeholders)와 그들의 특정 사용 사례 및 요구사항
- C API의 강점
- 9가지 약점 영역으로 분류된 C API의 문제점
이 문서는 식별된 문제에 대한 해결책을 제안하지 않습니다. 대신 C API 문제의 공유 목록을 작성함으로써, 향후 변경 제안에 대한 논의를 안내하고 평가 기준을 식별하는 데 도움을 줄 것입니다.
서론 (Introduction)
Python의 C API는 현재 수행하는 다양한 목적을 위해 설계되지 않았습니다. 이는 원래 인터프리터의 C 코드와 Python 언어 및 라이브러리 간의 내부 API에서 발전했습니다. 초기에는 Python을 C/C++ 애플리케이션에 임베드하고 C/C++로 확장 모듈(extension modules)을 작성할 수 있도록 노출되었습니다. 이러한 기능은 Python 생태계의 성장에 중요한 역할을 했습니다. 수십 년 동안 C API는 다양한 안정성 계층(tiers)을 제공하도록 성장했으며, 규칙(conventions)이 변경되었고, C/C++ 외 다른 언어에 대한 바인딩과 같은 새로운 사용 패턴이 등장했습니다.
향후 몇 년 동안 GIL 제거 및 JIT 컴파일러 개발과 같은 새로운 개발은 C API를 더욱 시험대에 올릴 것으로 예상됩니다. 그러나 이러한 성장은 명확하게 문서화된 가이드라인의 지원을 받지 못하여 CPython의 다양한 서브시스템에서 API 설계에 일관성 없는 접근 방식을 초래했습니다. 또한, CPython은 더 이상 유일한 Python 구현이 아니며, CPython이 유일했을 때 만들어진 일부 설계 결정은 대체 구현(alternative implementations)이 다루기 어렵습니다. 한편, C API의 설계 및 구현 모두에서 얻은 교훈과 식별된 오류가 있었습니다.
C API를 발전시키는 것은 하위 호환성(backwards compatibility) 제약과 기술적, 사회적 내재된 복잡성이 결합되어 어렵습니다. 다양한 유형의 사용자는 때때로 상충되는 다른 요구사항을 가지고 있습니다. 안정성과 발전 사이의 절충점은 점진적인 개선 제안이 있을 때마다 지속적으로 논쟁이 많은 주제입니다. C API의 개선, 재설계 또는 교체를 위한 여러 제안이 제시되었으며, 각각은 문제에 대한 심층적인 분석을 나타냅니다. 2023년 Language Summit에서는 C API의 다양한 측면에 세션이 연속으로 할애되었습니다. 새로운 설계가 지난 30년 동안 C API가 축적한 문제를 해결하는 동시에, 원래 설계되지 않았던 사용 사례에 맞게 업데이트할 수 있다는 일반적인 합의가 있었습니다.
하지만 Language Summit에서는 우리가 해결하려는 문제에 대한 명확한 공통된 이해 없이 해결책을 논의하려 한다는 인식이 있었습니다. 우리는 제안된 해결책을 평가하기 전에 C API의 현재 문제에 대해 합의해야 한다고 결정했습니다. 따라서 이 질문에 대한 모든 사람의 아이디어를 수집하기 위해 GitHub에 capi-workgroup
저장소(repository)를 만들었습니다.
해당 저장소에는 60개 이상의 다양한 이슈가 생성되었으며, 각각은 C API의 문제를 설명합니다. 우리는 이를 분류하고 여러 반복되는 주제를 식별했습니다. 아래 섹션들은 주로 이러한 주제에 해당하며, 각 섹션에는 해당 범주에서 제기된 이슈들의 통합된 설명과 개별 이슈에 대한 링크가 포함되어 있습니다. 또한, C API의 다양한 이해관계자와 각자가 가진 특정 요구사항을 식별하는 것을 목표로 하는 섹션도 포함했습니다.
C API 이해관계자 (C API Stakeholders)
서론에서 언급했듯이, C API는 원래 CPython 인터프리터와 Python 계층 간의 내부 인터페이스로 만들어졌습니다. 나중에 서드파티 개발자들이 Python 프로그램을 확장하고 임베드하는 방법으로 노출되었습니다. 수년 동안 새로운 유형의 이해관계자들이 등장했으며, 그들은 서로 다른 요구사항과 초점 영역을 가졌습니다. 이 섹션은 다양한 이해관계자들이 C API를 통해 수행해야 하는 작업의 관점에서 이 복잡한 상황을 설명합니다.
모든 이해관계자를 위한 공통 작업 (Common Actions for All Stakeholders)
모든 유형의 API 사용자가 필요로 하는 일반적인 작업은 다음과 같습니다.
- 함수 정의 및 호출
- 새로운 타입 정의
- 내장(builtin) 및 사용자 정의 타입의 인스턴스 생성
- 객체 인스턴스에 대한 작업 수행
- 타입, 인스턴스 및 함수를 포함한 객체 내성(Introspect)
- 예외 발생 및 처리
- 모듈 임포트
- Python의 OS 인터페이스 접근
다음 섹션에서는 다양한 이해관계자의 고유한 요구사항을 살펴봅니다.
확장 작성자 (Extension Writers)
확장 작성자는 C API의 전통적인 사용자입니다. 그들의 요구사항은 위에 나열된 공통 작업입니다. 또한 일반적으로 다음을 필요로 합니다.
- 새로운 모듈 생성
- C 레벨에서 모듈 간의 효율적인 인터페이스 구축
임베디드 Python 애플리케이션 작성자 (Authors of Embedded Python Applications)
Python 인터프리터가 임베디드된 애플리케이션의 작성자입니다. 예시로는 Blender 및 OBS가 있습니다.
이들은 다음을 수행할 수 있어야 합니다.
- 인터프리터 구성 (import 경로,
inittab
,sys.argv
, 메모리 할당자 등). - 클린 인터프리터 종료 및 재시작을 포함하여 실행 모델 및 프로그램 수명과 상호 작용.
- 깊은 복사본을 만들 필요 없이 Python이 사용할 수 있는 방식으로 복잡한 데이터 모델을 표현.
- 동결(frozen) 모듈 제공 및 임포트.
- 여러 독립적인 인터프리터 실행 및 관리 (특히 전역 효과를 피하고자 하는 라이브러리에 임베드될 때).
Python 구현체 (Python Implementations)
CPython, PyPy, GraalPy, IronPython, RustPython, MicroPython 및 Jython과 같은 Python 구현체는 다양한 서브시스템 구현에 대해 매우 다른 접근 방식을 취할 수 있습니다. 이들은 다음을 필요로 합니다.
- API가 추상적이고 구현 세부 정보를 숨길 것.
- API 사양, 이상적으로는 호환성을 보장하는 테스트 스위트와 함께 제공될 것.
- Python 구현체 간에 공유될 수 있는 ABI가 있다면 좋을 것입니다.
대체 API 및 바인딩 생성기 (Alternative APIs and Binding Generators)
C API에 대한 대안을 구현하는 여러 프로젝트가 있으며, 이들은 C API를 직접 프로그래밍하는 것보다 확장 사용자에게 이점을 제공합니다. 이러한 API는 C API를 사용하여 구현되며, 일부 경우에는 CPython 내부를 사용합니다.
또한 Python과 다른 객체 모델, 패러다임 또는 언어 간에 바인딩을 생성하는 라이브러리도 있습니다.
이러한 범주에는 중복이 있습니다. 바인딩 생성기는 일반적으로 대체 API를 제공하며 그 반대도 마찬가지입니다.
예시로는 C++용 Cython, cffi, pybind11, nanobind, Rust용 PyO3, Qt용 PySide에서 사용되는 Shiboken, GTK용 PyGObject, Go용 Pygolo, Java용 JPype, Android용 PyJNIus, Objective-C용 PyObjC, C/C++용 SWIG, .NET(C#)용 Python.NET, HPy, Mypyc, Pythran 및 pythoncapi-compat가 있습니다. CPython의 함수 인자 파싱을 위한 DSL인 Argument Clinic도 이 이해관계자 범주에 속하는 것으로 볼 수 있습니다.
대체 API는 CPython에 효율적으로 접근하기 위한 최소한의 빌딩 블록을 필요로 합니다. 일반적으로 사람이 읽을 의도로 생성된 코드가 아니기 때문에 인체공학적(ergonomic) API가 반드시 필요한 것은 아닙니다. 그러나 성능을 희생하지 않으면서 내부(internals) 접근을 피할 수 있도록 충분히 포괄적이어야 합니다.
바인딩 생성기는 종종 다음을 필요로 합니다.
- 동등한 Python 코드의 동작과 최대한 일치하는 사용자 정의 객체 (예: 함수/모듈 객체 및 traceback 항목) 생성.
- 전통적인 C 확장에서는 정적인 객체 (예: 클래스/모듈)를 동적으로 생성하고, CPython이 해당 상태 및 수명을 관리하도록 요구.
- 낮은 오버헤드로 외부 객체 (문자열, GC’d 컨테이너)를 동적으로 조정.
- 외부 메커니즘, 실행 모델 및 보장(guarantees)을 Python 방식에 맞게 조정 (stackful coroutines, continuations, one-writer-or-multiple-readers semantics, virtual multiple inheritance, 1-based indexing, super-long inheritance chains, goroutines, channels 등).
이러한 도구는 더 안정적인 API와 더 빠른 (아마도 더 낮은 수준의) API 중에서 선택할 수 있는 이점도 누릴 수 있습니다. 그러면 사용자들은 코드를 자주 재생성할 여유가 있는지, 아니면 더 많은 안정성과 적은 유지보수 작업을 위해 일부 성능을 절충할지 결정할 수 있습니다.
C API의 강점 (Strengths of the C API)
이 문서의 대부분은 새로운 설계에서 수정되기를 바라는 C API의 문제점에 할애되었지만, C API의 강점을 지적하고 이러한 강점이 유지되도록 하는 것도 중요합니다.
서론에서 언급했듯이, C API는 지난 30년 동안 Python 생태계의 개발과 성장을 가능하게 했으며, 원래 설계되지 않았던 사용 사례를 지원하도록 발전했습니다. 이러한 실적 자체는 C API가 얼마나 효과적이고 가치 있었는지를 보여주는 지표입니다.
capi-workgroup
논의에서 여러 특정 강점이 언급되었습니다. Heap 타입은 정적 타입보다 훨씬 안전하고 사용하기 쉬운 것으로 식별되었습니다. Python 문자열 기반의 조회(lookups)를 위해 C 문자열 리터럴을 사용하는 API 함수는 매우 편리합니다. 제한된 API(limited API)는 구현 세부 정보를 숨기는 API가 Python을 발전시키는 것을 더 쉽게 만든다는 것을 보여줍니다.
C API 문제점 (C API problems)
이 문서의 나머지 부분은 capi-workgroup
저장소에 보고된 문제점들을 요약하고 분류합니다. 이슈들은 여러 범주로 그룹화됩니다.
API 진화 및 유지보수 (API Evolution and Maintenance)
C API 변경의 어려움은 이 보고서의 핵심입니다. 이는 여기서 논의하는 많은 이슈에 내재되어 있으며, 특히 점진적인 버그 수정으로 문제가 해결될 수 있는지, 아니면 API 재설계의 일부로만 해결될 수 있는지 결정해야 할 때 더욱 그렇습니다. 각 점진적 변경의 이점은 종종 혼란을 정당화하기에는 너무 작게 여겨집니다. 시간이 지남에 따라 이는 API 설계 또는 구현에서 발생하는 모든 실수가 무기한으로 남아 있음을 의미합니다.
이 문제에 대해 두 가지 관점을 취할 수 있습니다. 하나는 이것이 문제이며, API 요소의 폐기(deprecation) 및 제거를 포함하는 점진적 API 진화를 위한 프로세스 형태로, 우리가 설계하는 모든 새로운 C API에 해결책이 포함되어야 한다는 것입니다. 다른 가능한 접근 방식은 이것이 해결해야 할 문제가 아니라 모든 API의 특징이라는 것입니다. 이 관점에서는 API 진화가 점진적이지 않고, 과거의 실수로부터 배우고 하위 호환성 요구사항에 얽매이지 않는 대규모 재설계를 통해 이루어져야 합니다 (그 동안 새로운 API 요소는 추가될 수 있지만, 아무것도 제거될 수 없습니다). 타협적인 접근 방식은 이 두 극단 사이 어딘가에 있으며, 점진적으로 해결하기 쉽거나 중요하다고 판단되는 문제를 수정하고, 다른 문제들은 그대로 둡니다.
CPython에서 우리가 가진 문제는 API 진화에 대한 합의되고 공식적인 접근 방식이 없다는 것입니다. 핵심 팀의 다른 구성원들이 다른 방향으로 이끌고 있으며, 이는 지속적인 의견 불일치의 원천입니다. 모든 새로운 C API는 유지보수가 따를 모델과 이를 위한 기술적, 조직적 프로세스에 대한 명확한 결정과 함께 제공되어야 합니다.
만약 모델에 API의 점진적 진화를 위한 조항이 포함된다면, 사용자에게 미치는 변경의 영향을 관리하기 위한 프로세스가 포함될 것입니다. 이는 아마도 외부 하위 호환성 모듈(external backwards compatibility module)을 도입하거나, “축복받은(blessed)” 함수들의 새로운 API 계층(tier)을 도입함으로써 이루어질 수 있습니다.
API 사양 및 추상화 (API Specification and Abstraction)
C API는 공식적인 사양이 없으며, 현재 특정 버전의 레퍼런스 구현(CPython)에 포함된 모든 것으로 정의됩니다. 문서는 불완전한 설명으로 작용하며, 전체 API, 제한된 API 또는 안정적인 ABI의 정확성을 확인하기에는 충분하지 않습니다. 결과적으로 C API는 더 눈에 띄는 사양 업데이트 없이 릴리스 간에 크게 변경될 수 있으며, 이는 여러 문제를 야기합니다.
C/C++ 외 다른 언어에 대한 바인딩은 C 코드를 파싱해야 합니다. 일부 C 언어 기능은 컴파일러 의존적인 출력을 생성하거나 (예: enums) 단순히 파서가 아닌 C 전처리기/컴파일러를 요구하기 때문에 (예: macros) 이러한 방식으로 처리하기 어렵습니다.
또한, C 헤더 파일은 의도된 Public API의 일부보다 더 많은 것을 노출하는 경향이 있습니다. 특히, 내부 데이터 구조의 정확한 메모리 레이아웃과 같은 구현 세부 정보가 노출될 수 있습니다. 이는 API 진화를 매우 어렵게 만들 수 있으며, 특히 참조 카운팅 매크로를 통해 접근되는 ob_refcnt
및 ob_type
의 경우와 같이 안정적인 ABI에서 발생하는 경우 더욱 그렇습니다.
참조 카운팅(reference counting)이 노출되는 방식과 관련하여 더 깊은 문제를 식별했습니다. C 확장이 Py_INCREF
및 Py_DECREF
호출을 사용하여 참조를 관리해야 하는 방식은 CPython의 메모리 모델에 특화되어 있으며, 대체 Python 구현이 에뮬레이션하기 어렵습니다.
또 다른 문제 세트는 PyObject*
가 C API에서 핸들(handle)이 아닌 실제 포인터로 노출된다는 사실에서 발생합니다. 객체의 주소는 해당 ID 역할을 하며 비교에 사용되는데, 이는 GC 중에 객체를 이동하는 대체 Python 구현에 문제를 복잡하게 만듭니다.
별개의 문제는 객체 참조가 런타임에 불투명하며, 자체 목적을 가진 tp_traverse
/ tp_clear
호출을 통해서만 검색 가능하다는 것입니다. 런타임이 객체 그래프의 구조를 알고 변경 사항을 따라갈 수 있는 방법이 있다면, 대체 구현이 다른 메모리 관리 체계를 구현하는 것을 가능하게 할 것입니다.
객체 참조 관리 (Object Reference Management)
참조의 의미를 명확하게 하는 함수에 대한 일관된 명명 규칙이 존재하지 않으며, 이는 일반적인 동작을 따르지 않는 C API 함수에서 오류 발생 가능성을 높입니다. C API 함수가 PyObject*
를 반환할 때, 호출자는 일반적으로 객체에 대한 참조 소유권을 얻습니다. 그러나 함수가 “빌린(borrowed)” 참조를 반환하는 예외가 있으며, 호출자는 이 참조에 접근할 수 있지만 소유하지는 않습니다. 유사하게, 함수는 일반적으로 인자에 대한 참조 소유권을 변경하지 않지만, 함수가 참조를 “훔치는(steals)” 예외가 있습니다. 즉, 호출 시 참조의 소유권이 호출자에서 피호출자로 영구적으로 이전됩니다. 이러한 상황을 설명하는 데 사용되는 용어도 개선될 수 있습니다.
“빌린” 참조를 반환하는 함수 (예: PyList_GetItem
) 또는 객체의 내부 구조 일부에 대한 포인터를 반환하는 함수 (예: PyBytes_AsString
)의 경우 더 근본적인 변경이 필요합니다. 두 경우 모두, 참조/포인터는 소유 객체가 참조를 보유하는 동안만 유효하지만, 이 시간을 추론하기는 어렵습니다. 이러한 함수는 안전하게 만들 수 있는 메커니즘 없이는 API에 존재해서는 안 됩니다.
컨테이너의 경우, 현재 API에는 포함된 객체의 참조에 대한 대량 작업(bulk operations)이 누락되어 있습니다. INCREF
및 DECREF
가 매크로일 수 없는 안정적인 ABI의 경우 특히 중요하며, 함수 호출 시퀀스로 구현될 때 대량 작업 비용이 많이 듭니다.
타입 정의 및 객체 생성 (Type Definition and Object Creation)
C API에는 PyTuple_New
및 PyUnicode_New
와 같이 불완전하거나 일관성 없는 Python 객체를 생성할 수 있는 함수가 있습니다. 이는 객체가 GC에 의해 추적되거나 tp_traverse
/ tp_clear
함수가 호출될 때 문제를 일으킵니다. 관련 이슈는 부분적으로 초기화된 튜플을 수정하는 데 사용되는 PyTuple_SetItem
과 같은 함수에 있습니다 (튜플은 완전히 초기화되면 변경 불가능합니다).
타입 정의 API와 관련하여 몇 가지 문제를 식별했습니다. 레거시(legacy) 문제로 인해 tp_new
와 tp_vectorcall
사이에 상당한 코드 중복이 발생하는 경우가 많습니다. 타입 슬롯 함수는 간접적으로 호출되어야 하므로, 서명이 컨텍스트 정보를 포함하도록 변경될 수 있습니다. 타입 정의 및 생성 프로세스의 여러 측면, 예를 들어 프로세스의 어느 단계가 타입 객체의 다양한 필드를 초기화하고 지우는 역할을 하는지 등은 잘 정의되어 있지 않습니다.
오류 처리 (Error Handling)
C API의 오류 처리는 스레드 상태(전역 범위)에 저장된 오류 표시기(error indicator)를 기반으로 합니다. 설계 의도는 각 API 함수가 오류 발생 여부를 나타내는 값을 반환하는 것이었습니다 (관례적으로 -1 또는 NULL
). 프로그램이 오류가 발생했음을 알면 오류 표시기에 저장된 예외 객체를 가져올 수 있습니다. 우리는 오류 처리와 관련된 여러 문제를 식별했으며, 잘못 사용하기 너무 쉬운 API를 지적합니다.
실행 중에 발생하는 모든 오류를 보고하지 않는 함수가 있습니다. 예를 들어, PyDict_GetItem
은 키의 해시 함수를 호출하거나 딕셔너리에서 조회를 수행할 때 발생하는 모든 오류를 지웁니다.
Python 코드는 in-flight exception
(정의상)으로 실행되지 않으며, 일반적으로 네이티브 함수를 사용하는 코드도 예외가 발생하면 중단되어야 합니다. 이는 대부분의 C API 함수에서 확인되지 않으며, 인터프리터에는 예외가 설정된 상태에서 오류 처리 코드가 C API 함수를 호출하는 곳이 있습니다. 예를 들어, _PyErr_WriteUnraisableMsg
의 오류 핸들러에서 PyUnicode_FromString
호출을 참조하십시오.
값을 반환하지 않는 함수가 있어서, 호출자는 오류 발생 여부를 확인하기 위해 오류 표시기를 쿼리해야 합니다. PyBuffer_Release
가 그 예입니다. 반환 값은 있지만 오류 발생 여부를 명확하게 나타내지 않는 다른 함수도 있습니다. 예를 들어, PyLong_AsLong
은 오류가 발생하거나 인자 값이 실제로 -1일 경우 -1을 반환합니다. 두 경우 모두, 함수가 호출되기 전에 오류 표시기가 이미 설정되어 있었고 오류가 잘못 귀속될 가능성이 있기 때문에 API는 오류 발생 가능성이 높습니다. 호출 전에 오류가 감지되지 않은 것은 호출 코드의 버그이지만, 이 경우 프로그램의 동작은 문제를 식별하고 디버그하기 어렵게 만듭니다.
PyObject*
인자를 받는 함수가 있으며, NULL
일 때 특별한 의미를 가집니다. 예를 들어, PyObject_SetAttr
이 설정할 값으로 NULL
을 받으면 속성이 지워져야 함을 의미합니다. 이는 NULL
이 값 구성의 오류를 나타낼 수 있고 프로그램이 이 오류를 확인하지 못했을 수 있기 때문에 오류 발생 가능성이 높습니다. 프로그램은 NULL
을 오류와 다른 의미로 잘못 해석할 것입니다.
API 계층 및 안정성 보장 (API Tiers and Stability Guarantees)
다양한 API 계층은 안정성 대 API 진화, 그리고 때로는 성능에 대한 다른 트레이드오프를 제공합니다.
안정적인 ABI(stable ABI)는 검토가 필요한 영역으로 식별되었습니다. 현재 불완전하고 널리 채택되지 않았습니다. 동시에 ob_refcnt
, ob_type
, ob_size
와 같은 구조체 필드를 노출하기 때문에 일부 구현 세부 사항을 변경하기 어렵게 만듭니다. 안정적인 ABI를 유지할 가치가 있는지에 대한 논의가 있었습니다. 양측의 주장은 [Issue 4] 및 [Issue 9]에서 찾을 수 있습니다.
대안으로, 안정적인 ABI를 발전시키려면 동일한 Python 바이너리에서 여러 버전을 지원하는 메커니즘이 필요하다는 제안이 있었습니다. 단일 ABI 버전 내에서 개별 함수를 버전 관리하는 것만으로는 충분하지 않다는 점이 지적되었습니다. 서로 상호 운용되는 함수 그룹을 함께 발전시켜야 할 수도 있기 때문입니다.
제한된 API(limited API)는 3.2에서 자주 변경될 가능성이 없는 고품질 API로 자신을 제한하려는 사용자에게 권장되는 C API의 “축복받은(blessed)” 하위 집합으로 도입되었습니다. Py_LIMITED_API
플래그는 사용자가 자신의 프로그램을 이전 버전의 제한된 API로 제한할 수 있도록 하지만, 이제는 이전 버전을 제외하는 반대 옵션이 필요합니다. 이는 제한된 API의 결함 있는 요소를 교체함으로써 제한된 API를 발전시키는 것을 가능하게 할 것입니다. 보다 일반적으로, 재설계에서는 API 계층이 지정되는 방식을 재검토하고 현재 다른 계층 간에 선택하는 방식을 통합할 방법을 설계하는 것을 고려해야 합니다.
이름이 밑줄로 시작하는 API 요소는 private으로 간주되며, 본질적으로 안정성 보장이 없는 API 계층입니다. 그러나 이는 최근 PEP 689에서야 명확해졌습니다. PEP 689 이전에 존재했던 이러한 API 요소에 대한 변경 정책이 무엇이어야 하는지는 명확하지 않습니다.
오류 검사를 수행하는 안전한 버전과 함께 안전하지 않지만 빠른 버전도 있는 API 함수가 있습니다 (예: PyTuple_GET_ITEM
대 PyTuple_GetItem
). 이들을 “unsafe API” 계층과 “safe API” 계층으로 자체 계층으로 그룹화하는 것이 도움이 될 수 있습니다.
C 언어 사용 (Use of the C Language)
CPython이 C 언어를 사용하는 방식과 관련하여 여러 문제가 제기되었습니다. 첫째, 우리가 사용하는 C 방언(dialect)과 C++ 방언과의 호환성을 어떻게 테스트하는지, 그리고 API 헤더의 C++ 방언과의 호환성 문제가 있습니다.
API에서 const
사용은 현재 드물지만, 이를 변경하는 것을 고려해야 하는지 여부는 명확하지 않습니다.
현재 우리는 long
및 int
C 타입을 사용하고 있는데, int32_t
및 int64_t
와 같은 고정 너비 정수(fixed-width integers)가 이제 더 나은 선택일 수 있습니다.
매크로, 가변 인자(variadic arguments), enums, 비트필드(bitfields) 및 비함수 기호(non-function symbols)와 같이 다른 언어와 상호 작용하기 어려운 C 언어 기능을 사용하고 있습니다.
PyObject*
인자를 받지만 더 구체적인 타입이어야 하는 API 함수가 있습니다 (예: 인자가 PyTupleObject*
가 아니면 실패하는 PyTuple_Size
). 이것이 좋은 패턴인지, 아니면 API가 더 구체적인 타입을 기대해야 하는지는 미해결 질문입니다.
API에는 PyDict_GetItemString
과 같이 PyObject*
가 아닌 C 문자열로 지정된 키에 대한 딕셔너리 조회를 수행하는 구체적인 타입(concrete types)을 받는 함수가 있습니다. 동시에 PyDict_ContainsString
의 경우 구체적인 타입 대안을 추가하는 것은 적절하지 않다고 간주됩니다. 이에 대한 원칙은 가이드라인에 문서화되어야 합니다.
구현 결함 (Implementation Flaws)
다음은 국지적인 구현 결함 목록입니다. 이들 대부분은 우리가 선택한다면 점진적으로 수정될 수 있을 것입니다. 어쨌든, 새로운 API 설계에서는 이러한 결함을 피해야 합니다.
성공 시 0, 실패 시 -1을 반환하는 규칙을 따르지 않는 함수가 있습니다. 예를 들어, PyArg_ParseTuple
은 성공 시 0, 실패 시 0이 아닌 값을 반환합니다.
매크로 Py_CLEAR
및 Py_SETREF
는 인자를 한 번 이상 접근하므로, 인자가 부작용(side effects)이 있는 표현식인 경우 중복됩니다.
Py_SIZE
의 의미는 타입에 따라 다르며 항상 신뢰할 수 있는 것은 아닙니다.
일부 API 함수는 해당 Python 동등물과 동일한 동작을 하지 않습니다. PyIter_Next
의 동작은 tp_iternext
와 다릅니다. PySet_Contains
의 동작은 set.__contains__
와 다릅니다.
PyArg_ParseTupleAndKeywords
가 non-const char*
배열을 인자로 받는다는 사실은 사용하기 더 어렵게 만듭니다.
Python.h
는 전체 API를 노출하지 않습니다. 일부 헤더 (예: marshal.h
)는 Python.h
에서 포함되지 않습니다.
명명 (Naming)
PyLong
및 PyUnicode
는 더 이상 그들이 나타내는 Python 타입 (int
/ str
)과 일치하지 않는 이름을 사용합니다. 이는 새로운 API에서 수정될 수 있습니다.
API에는 Py
/ _Py
접두사가 없는 식별자가 있습니다.
누락된 기능 (Missing Functionality)
이 섹션은 기능 요청, 즉 현재 C API에서 누락된 것으로 식별된 기능 목록으로 구성됩니다.
디버그 모드 (Debug Mode)
재컴파일 없이 활성화할 수 있으며 다양한 유형의 오류를 감지하는 데 도움이 될 수 있는 다양한 검사를 활성화하는 디버그 모드.
내성 (Introspection)
Python 객체와 동일한 방식으로 C로 정의된 객체에 대한 신뢰할 수 있는 내성 기능이 현재 없습니다.
힙 타입(heap types)에 대한 효율적인 타입 검사 기능.
다른 언어와의 개선된 상호 작용 (Improved Interaction with Other Languages)
다른 GC 기반 언어와의 인터페이스 및 해당 GC를 Python의 GC와 통합하는 기능.
트레이스백(traceback)에 외부 스택 프레임 주입.
다른 언어에서 사용할 수 있는 구체적인 문자열.
참고 자료 (References)
- Python/C API Reference Manual
- 2023 Language Summit Blog Post: Three Talks on the C API
- capi-workgroup on GitHub
- Irit’s Core Sprint 2023 slides about C API workgroup
- Petr’s Core Sprint 2023 slides
- HPy team’s Core Sprint 2023 slides for Things to Learn from HPy
- Victor’s slides of Core Sprint 2023 Python C API talk
- The Python’s stability promise — Cristián Maureira-Fredes, PySide maintainer
- Report on the issues PySide had 5 years ago when switching to the stable ABI
저작권 (Copyright)
이 문서는 퍼블릭 도메인 또는 CC0-1.0-Universal 라이선스 중 더 관대한 조건에 따라 배포됩니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments