Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 733 – Python 공개 C API에 대한 평가

Author:
Erlend Egeberg Aasland <erlend at python.org>, Domenico Andreoli <domenico.andreoli at linux.com>, Stefan Behnel <stefan_ml at behnel.de>, Carl Friedrich Bolz-Tereick <cfbolz at gmx.de>, Simon Cross <hodgestar at gmail.com>, Steve Dower <steve.dower at python.org>, Tim Felgentreff <tim.felgentreff at oracle.com>, David Hewitt <1939362+davidhewitt at users.noreply.github.com>, Shantanu Jain <hauntsaninja at gmail.com>, Wenzel Jakob <wenzel.jakob at epfl.ch>, Irit Katriel <irit at python.org>, Marc-Andre Lemburg <mal at lemburg.com>, Donghee Na <donghee.na at python.org>, Karl Nelson <nelson85 at llnl.gov>, Ronald Oussoren <ronaldoussoren at mac.com>, Antoine Pitrou <solipsis at pitrou.net>, Neil Schemenauer <nas at arctrix.com>, Mark Shannon <mark at hotpy.org>, Stepan Sindelar <stepan.sindelar at oracle.com>, Gregory P. Smith <greg at krypto.org>, Eric Snow <ericsnowcurrently at gmail.com>, Victor Stinner <vstinner at python.org>, Guido van Rossum <guido at python.org>, Petr Viktorin <encukou at gmail.com>, Carol Willing <willingc at gmail.com>, William Woodruff <william at yossarian.net>, David Woods <dw-git at d-woods.co.uk>, Jelle Zijlstra <jelle.zijlstra at gmail.com>
Status:
Final
Type:
Informational
Created:
16-Oct-2023
Post-History:
01-Nov-2023

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 정보성 PEP는 공개 C API에 대해 우리가 공유하는 관점을 기술합니다. 이 문서는 다음을 정의합니다.

  • C API의 목적
  • 이해관계자와 각자의 구체적인 사용 사례 및 요구 사항
  • C API의 강점
  • 아홉 가지 약점 영역으로 분류한 C API의 문제점

이 문서는 식별된 문제 중 어느 것에 대해서도 해결책을 제안하지 않습니다. 이 문서는 C API 문제의 공동 목록을 만들어, 변경 제안에 관한 지속적인 논의를 이끌고 평가 기준을 찾는 데 도움을 줄 것입니다.

소개

Python의 C API는 현재 수행하고 있는 다양한 목적을 위해 설계되지 않았습니다. C API는 처음에 인터프리터의 C 코드와 Python 언어 및 라이브러리 사이의 내부 API였던 것에서 발전했습니다. 초기 형태의 C API는 Python을 C/C++ 애플리케이션에 내장하고 C/C++로 확장 모듈을 작성할 수 있도록 공개되었습니다. 이러한 기능은 Python 생태계의 성장에 중요한 역할을 했습니다. 수십 년에 걸쳐 C API는 서로 다른 수준의 안정성을 제공하도록 확장되었고, 관례가 바뀌었으며, C/C++ 이외의 언어에 대한 바인딩과 같은 새로운 사용 패턴이 등장했습니다. 앞으로 몇 년 동안 GIL 제거와 JIT 컴파일러 개발 같은 새로운 변화가 C API를 한층 더 시험할 것으로 예상됩니다. 그러나 이러한 성장은 명확하게 문서화된 지침의 뒷받침을 받지 못했고, 그 결과 CPython의 여러 하위 시스템에서 API 설계 방식이 일관되지 않게 되었습니다. 또한 CPython은 더 이상 파이썬의 유일한 구현이 아니며, CPython이 유일한 구현이던 시절에 내려진 설계 결정 중 일부는 대체 구현이 다루기 어렵습니다 [Issue 64]. 그동안 여러 교훈을 얻었고, C API의 설계와 구현 양쪽에서 실수가 확인되었습니다.

하위 호환성 제약에 기술적·사회적 측면 모두에서 C API에 내재한 복잡성이 더해져, C API를 발전시키기가 어렵습니다. 사용자 유형마다 서로 다르고 때로는 상충하는 요구 사항을 제시합니다. 점진적 개선이 제안될 때마다 안정성과 발전 사이의 절충은 계속해서 격렬한 논쟁이 벌어지는 논의 주제입니다. C API의 개선, 재설계 또는 대체를 위한 여러 제안이 나왔으며, 각 제안은 문제를 깊이 분석한 결과입니다. 2023년 언어 서밋에서는 연달아 열린 세 세션이 C API의 여러 측면을 다루었습니다. 새로운 설계가 지난 30년 동안 C API에 누적된 문제를 해결하는 동시에, 원래 설계 목적에 없던 사용 사례에 맞게 C API를 갱신할 수 있다는 데 대체로 의견이 일치합니다.

그러나 언어 서밋에서는 우리가 해결하려는 문제를 명확하게 함께 이해하지 못한 채 해결책을 논의하려 한다는 인식도 있었습니다. 우리는 제안된 해결책을 평가하기에 앞서 C API의 현재 문제점에 대해 먼저 합의해야 한다고 결정했습니다. 그래서 우리는 이 질문에 대한 모두의 생각을 모으기 위해 GitHub에 capi-workgroup 저장소를 만들었습니다.

해당 저장소에는 60개가 넘는 서로 다른 이슈가 등록되었으며, 각 이슈는 C API의 문제 하나를 설명합니다. 우리는 이 이슈들을 분류하고, 반복적으로 나타나는 여러 주제를 파악했습니다. 아래 섹션들은 대부분 이 주제들에 대응하며, 각 섹션에는 해당 범주에서 제기된 이슈들을 종합한 설명과 개별 이슈로의 링크가 포함되어 있습니다. 또한 C API의 다양한 이해관계자와 각 이해관계자가 가진 고유한 요구 사항을 파악하기 위한 섹션도 포함했습니다.

C API 이해관계자

서론에서 언급했듯이, C API는 원래 CPython의 인터프리터와 Python 계층 사이의 내부 인터페이스로 만들어졌습니다. 이후 서드파티 개발자가 Python 프로그램을 확장하고 임베드할 수 있는 수단으로 공개되었습니다. 수년에 걸쳐 서로 다른 요구 사항과 관심 영역을 가진 새로운 유형의 이해관계자들이 등장했습니다. 이 섹션에서는 여러 이해관계자가 C API를 통해 수행해야 하는 작업의 관점에서 이 복잡한 상황을 설명합니다.

모든 이해관계자에게 공통된 작업

일반적이며 모든 유형의 API 사용자에게 필요한 작업들이 있습니다:

  • 함수를 정의하고 호출하기
  • 새로운 타입 정의하기
  • 내장 타입과 사용자 정의 타입의 인스턴스 생성하기
  • 객체 인스턴스에 대한 연산 수행하기
  • 타입, 인스턴스, 함수를 포함한 객체를 인트로스펙션하기
  • 예외를 발생시키고 처리하기
  • 모듈 임포트하기
  • Python의 OS 인터페이스에 접근하기

다음 섹션들에서는 다양한 이해관계자의 고유한 요구 사항을 살펴봅니다.

확장 모듈 작성자

확장 모듈 작성자는 C API의 전통적인 사용자입니다. 이들의 요구 사항은 위에 나열한 공통 작업입니다. 이들은 또한 일반적으로 다음 작업이 필요합니다:

  • 새로운 모듈 생성하기
  • C 수준에서 모듈 간에 효율적으로 인터페이스하기

임베디드 Python 애플리케이션 작성자

Python 인터프리터를 임베드한 애플리케이션입니다. 예로는 Blender와 OBS가 있습니다.

이들은 다음 작업을 할 수 있어야 합니다:

  • 인터프리터 구성하기(임포트 경로, inittab, sys.argv, 메모리 할당자 등).
  • 인터프리터의 깔끔한 종료와 재시작을 포함하여 실행 모델 및 프로그램 수명 주기와 상호 작용하기.
  • 깊은 복사본을 만들지 않고도 Python이 사용할 수 있는 방식으로 복잡한 데이터 모델을 표현합니다.
  • 동결된 모듈을 제공하고 임포트합니다.
  • 서로 독립적인 여러 인터프리터를 실행하고 관리합니다(특히 전역적인 영향을 피하려는 라이브러리에 임베드된 경우).

Python 구현

CPython, PyPy, GraalPy, IronPython, RustPython, MicroPython, Jython)과 같은 Python 구현은 여러 서브시스템을 구현할 때 매우 다른 접근 방식을 택할 수 있습니다. 이들에게는 다음이 필요합니다.

  • API가 추상적이어서 구현 세부 사항을 숨겨야 합니다.
  • API 명세가 필요하며, 이상적으로는 호환성을 보장하는 테스트 스위트도 함께 있어야 합니다.
  • 여러 Python 구현 간에 공유할 수 있는 ABI가 있으면 좋을 것입니다.

대체 API와 바인딩 생성기

C API의 대안을 구현하는 프로젝트가 여럿 있으며, 이들은 C API로 직접 프로그래밍하는 것보다 나은 이점을 확장 사용자에게 제공합니다. 이러한 API는 C API를 사용하여 구현되며, 경우에 따라 CPython 내부를 사용하기도 합니다.

Python과 다른 객체 모델, 패러다임 또는 언어 사이의 바인딩을 만드는 라이브러리도 있습니다.

이 범주들은 서로 겹칩니다. 바인딩 생성기는 대개 대체 API를 제공하며, 그 반대도 마찬가지입니다.

예로는 Cython, cffi, C++용 pybind11 및 nanobind, Rust용 PyO3, PySide가 Qt용으로 사용하는 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에 효율적으로 접근하기 위한 최소한의 구성 요소가 필요합니다. 이들은 일반적으로 사람이 읽도록 의도되지 않은 코드를 생성하므로, 반드시 사용하기 편리한 API가 필요하지는 않습니다. 하지만 성능을 희생하지 않으면서도 내부에 접근하지 않아도 될 만큼 API가 충분히 포괄적이어야 합니다.

바인딩 생성기는 흔히 다음과 같은 작업을 해야 합니다.

  • 동등한 Python 코드의 동작과 최대한 가깝게 일치하는 사용자 정의 객체(예: 함수/모듈 객체 및 트레이스백 항목)를 생성합니다.
  • 전통적인 C 확장에서는 정적인 객체(예: 클래스/모듈)를 동적으로 생성하며, CPython이 그 상태와 수명을 관리해야 합니다.
  • 외부 객체(문자열, GC 대상 컨테이너)를 낮은 오버헤드로 동적으로 적응시킵니다.
  • 외부 메커니즘, 실행 모델, 보장을 Python 방식에 맞게 조정합니다(스택풀 코루틴, 컨티뉴에이션, 단일 작성자 또는 다중 읽기자 의미, 가상 다중 상속, 1부터 시작하는 인덱싱, 매우 긴 상속 체인, 고루틴, 채널 등).

이러한 도구는 더 안정적인 API와 더 빠른(아마도 더 저수준의) API 중에서 선택할 수 있다면 이점을 얻을 수도 있습니다. 그러면 사용자는 코드를 자주 재생성하는 것을 감당할 수 있는지, 아니면 성능을 일부 포기하는 대신 더 높은 안정성과 더 적은 유지 보수 작업을 택할지 결정할 수 있습니다.

C API의 강점

이 문서의 대부분은 새로운 설계에서 해결되기를 바라는 C API의 문제점에 할애되어 있지만, C API의 강점을 짚고 그 강점이 유지되도록 하는 것 또한 중요합니다.

서론에서 언급했듯이, C API는 지난 30년 동안 Python 생태계의 발전과 성장을 가능하게 했으며, 원래 설계 목적이 아니었던 사용 사례까지 지원하도록 진화해 왔습니다. 이러한 실적 자체가 C API가 얼마나 효과적이고 가치 있었는지를 보여 줍니다.

capi-workgroup 논의에서 여러 구체적인 강점이 언급되었습니다. 힙 타입은 정적 타입보다 훨씬 안전하고 사용하기 쉬운 것으로 확인되었습니다 [Issue 4].

Python 문자열을 기반으로 조회할 때 C 문자열 리터럴을 받는 API 함수는 매우 편리합니다 [Issue 30].

제한된 API는 구현 세부 사항을 숨기는 API가 Python을 더 쉽게 발전시킬 수 있게 해 준다는 것을 보여 줍니다 [Issue 30].

C API의 문제점

이 문서의 나머지 부분에서는 capi-workgroup 저장소에 보고된 문제들을 요약하고 분류합니다. 문제들은 몇 가지 범주로 묶여 있습니다.

API 발전과 유지 관리

C API를 변경하기 어렵다는 점이 이 보고서의 핵심입니다. 이 어려움은 여기서 다루는 많은 문제에 암묵적으로 깔려 있으며, 특히 점진적인 버그 수정으로 문제를 해결할 수 있는지, 아니면 API 재설계의 일부로만 다룰 수 있는지를 결정해야 할 때 그렇습니다 [Issue 44]. 개별 점진적 변경으로 얻는 이점은 그로 인한 혼란을 정당화하기에는 너무 작다고 여겨지는 경우가 많습니다. 결국 이는 API 설계나 구현에서 저지른 모든 실수가 무기한 남게 된다는 것을 의미합니다.

이 문제에 대해서는 두 가지 관점을 취할 수 있습니다. 첫 번째 관점은 이것이 문제이며, 우리가 설계하는 모든 새 C API에 API 요소의 폐지와 제거를 포함하는 점진적 API 발전 프로세스의 형태로 해결책이 내재되어야 한다는 것입니다. 가능한 다른 접근 방식은 이것이 해결해야 할 문제가 아니라 모든 API가 가진 특성이라고 보는 것입니다. 이 관점에서 API 발전은 점진적으로 이루어져서는 안 되며, 대규모 재설계를 통해 이루어져야 합니다. 각 재설계는 과거의 실수에서 교훈을 얻으며 하위 호환성 요구 사항에 얽매이지 않습니다(그 사이에 새 API 요소를 추가할 수는 있지만, 어떤 것도 제거할 수는 없습니다). 절충적 접근 방식은 이 두 극단 사이 어딘가에 있으며, 점진적으로 다룰 만큼 쉽거나 중요한 문제는 수정하고 나머지는 그대로 둡니다.

CPython이 가진 문제는 API 발전에 대해 합의된 공식 접근 방식이 없다는 것입니다. 코어 팀의 구성원들이 서로 다른 방향으로 끌어당기고 있으며, 이것이 계속해서 의견 충돌의 원인이 되고 있습니다. 모든 새 C API에는 그 유지 관리가 따를 모델에 대한 명확한 결정과 함께, 이를 실행할 기술적·조직적 프로세스가 마련되어야 합니다.

모델에 API의 점진적 발전에 대한 규정이 포함된다면, 변경이 사용자에게 미치는 영향을 관리하는 프로세스도 포함될 것입니다 [Issue 60]. 이는 외부 하위 호환성 모듈을 도입하거나 [Issue 62], “공인된” 함수로 이루어진 새 API 계층을 도입하는 방식이 될 수 있습니다 [Issue 55].

API 명세와 추상화

C API에는 공식 명세가 없으며, 현재는 참조 구현(CPython)의 특정 버전에 들어 있는 것이 곧 C API로 정의됩니다. 문서는 불완전한 설명에 불과하므로 전체 API, 제한된 API, 안정 ABI 중 어느 것의 정확성도 검증하기에 충분하지 않습니다. 그 결과 C API는 더 눈에 띄는 명세 갱신 없이도 릴리스 사이에 크게 바뀔 수 있으며, 이로 인해 여러 문제가 생깁니다.

C/C++ 이외의 언어를 위한 바인딩은 C 코드를 구문 분석해야 합니다 [Issue 7]. 일부 C 언어 기능은 컴파일러에 따라 달라지는 출력을 만들거나(열거형 등) 단순한 구문 분석기가 아니라 C 전처리기/컴파일러를 필요로 하기 때문에(매크로 등) 이런 방식으로 처리하기 어렵습니다 [Issue 35].

게다가 C 헤더 파일은 공개 API의 일부로 의도된 것보다 더 많은 것을 노출하는 경향이 있습니다 [Issue 34]. 특히 내부 자료 구조의 정확한 메모리 레이아웃과 같은 구현 세부 사항이 노출될 수 있습니다 [Issue 22 및 PEP 620]. 이는 API 발전을 매우 어렵게 만들 수 있으며, 특히 참조 카운팅 매크로를 통해 접근하는 ob_refcnt와 ob_type의 경우처럼 안정 ABI에서 이런 일이 일어날 때 그렇습니다 [Issue 45].

우리는 참조 카운팅이 노출되는 방식과 관련하여 더 근본적인 문제를 확인했습니다. C 확장이 Py_INCREF와 Py_DECREF를 호출하여 참조를 관리해야 하는 방식은 CPython의 메모리 모델에 특화되어 있어, 대체 Python 구현이 이를 흉내 내기 어렵습니다. [Issue 12].

또 다른 문제들은 PyObject*가 C API에서 핸들이 아니라 실제 포인터로 노출된다는 사실에서 비롯됩니다. 객체의 주소가 그 객체의 ID 역할을 하며 비교에 사용되는데, 이는 GC 중에 객체를 이동시키는 대체 Python 구현에서 문제를 복잡하게 만듭니다 [Issue 37].

별개의 문제로, 객체 참조는 런타임에 불투명하며 각자 고유한 목적을 가진 tp_traverse/tp_clear 호출을 통해서만 파악할 수 있습니다. 런타임이 객체 그래프의 구조를 알고 그 변화를 계속 추적할 방법이 있다면, 대안 구현이 서로 다른 메모리 관리 방식을 구현할 수 있게 될 것입니다 [Issue 33].

객체 참조 관리

함수의 참조 의미를 명확히 드러내는 일관된 명명 규칙이 존재하지 않으며, 이 때문에 일반적인 동작을 따르지 않아 오류를 일으키기 쉬운 C API 함수가 생깁니다. C API 함수가 PyObject*를 반환하면, 호출자는 일반적으로 해당 객체에 대한 참조의 소유권을 얻습니다. 그러나 함수가 “빌린(borrowed)” 참조를 반환하는 예외도 있는데, 이 경우 호출자는 객체에 접근할 수는 있지만 그 참조를 소유하지는 않습니다. 마찬가지로, 함수는 일반적으로 인자에 대한 참조의 소유권을 변경하지 않지만, 함수가 참조를 “훔치는(steals)” 예외도 있습니다. 즉, 호출에 의해 참조의 소유권이 호출자에서 피호출자로 영구적으로 이전됩니다 [Issue 8 및 Issue 52]. 문서에서 이러한 상황을 설명하는 데 사용하는 용어도 개선할 수 있습니다 [Issue 11].

“빌린” 참조를 반환하는 함수(PyList_GetItem 등) [Issue 5 및 Issue 21]나 객체 내부 구조의 일부를 가리키는 포인터를 반환하는 함수(PyBytes_AsString 등) [Issue 57]의 경우에는 더 근본적인 변경이 필요합니다. 두 경우 모두 참조/포인터는 소유 객체가 참조를 보유하는 동안 유효하지만, 그 기간을 추론하기는 어렵습니다. 이러한 함수를 안전하게 만들 수 있는 메커니즘 없이는 이러한 함수가 API에 존재해서는 안 됩니다.

컨테이너의 경우, 현재 API에는 포함된 객체의 참조에 대한 일괄 연산이 없습니다. 이는 INCREF와 DECREF가 매크로일 수 없는 안정 ABI에서 특히 중요한데, 일괄 연산을 일련의 함수 호출로 구현하면 비용이 커지기 때문입니다 [Issue 15].

타입 정의와 객체 생성

C API에는 PyTuple_New와 PyUnicode_New처럼 불완전하거나 일관성 없는 Python 객체를 생성할 수 있게 하는 함수가 있습니다. 이는 객체가 GC에 의해 추적되거나 객체의 tp_traverse/tp_clear 함수가 호출될 때 문제를 일으킵니다. 이와 관련된 문제로, 부분적으로 초기화된 튜플을 수정하는 데 사용되는 PyTuple_SetItem 같은 함수가 있습니다(튜플은 완전히 초기화되고 나면 불변입니다) [Issue 56].

우리는 타입 정의 API에서 몇 가지 문제를 확인했습니다. 레거시상의 이유로, tp_new와 tp_vectorcall 사이에 상당한 양의 코드 중복이 있는 경우가 많습니다 [Issue 24]. 타입 슬롯 함수는 간접적으로 호출되어야 하며, 그래야 컨텍스트 정보를 포함하도록 시그니처를 변경할 수 있습니다 [Issue 13]. 타입 정의 및 생성 과정의 여러 측면이 명확하게 정의되어 있지 않습니다. 예를 들어 과정의 어느 단계가 타입 객체의 각 필드를 초기화하고 정리하는 책임을 지는지가 불분명합니다 [Issue 49].

오류 처리

C API의 오류 처리는 (전역 범위의) 스레드 상태에 저장되는 오류 표시기를 기반으로 합니다. 설계 의도는 각 API 함수가 오류 발생 여부를 나타내는 값(관례상 -1 또는 NULL)을 반환하는 것이었습니다. 프로그램은 오류가 발생했음을 알게 되면, 오류 표시기에 저장된 예외 객체를 가져올 수 있습니다. 우리는 오류 처리와 관련된 여러 문제를 확인했으며, 이 문제들은 잘못 사용하기 너무 쉬운 API를 가리킵니다.

실행 중에 발생하는 모든 오류를 보고하지는 않는 함수들이 있습니다. 예를 들어, PyDict_GetItem은 키의 해시 함수를 호출할 때나 딕셔너리에서 조회를 수행하는 동안 발생하는 모든 오류를 지워 버립니다 [Issue 51].

Python 코드는 (정의상) 처리 중인 예외가 있는 상태에서 실행되는 일이 결코 없으며, 일반적으로 네이티브 함수를 사용하는 코드도 오류가 발생하면 중단되어야 합니다. 대부분의 C API 함수에서는 이를 검사하지 않으며, 인터프리터에는 예외가 설정된 상태에서 오류 처리 코드가 C API 함수를 호출하는 곳들이 있습니다. 예를 들어, _PyErr_WriteUnraisableMsg의 오류 핸들러에서 PyUnicode_FromString을 호출하는 부분을 참고하십시오 [Issue 2].

값을 반환하지 않는 함수들이 있어서, 호출자는 오류가 발생했는지 확인하려면 오류 표시기를 조회할 수밖에 없습니다. 그 예로 PyBuffer_Release가 있습니다 [Issue 20]. 반환값이 있기는 하지만 그 반환값이 오류 발생 여부를 명확하게 나타내지 않는 함수도 있습니다. 예를 들어 PyLong_AsLong은 오류가 발생한 경우에도, 인자의 값이 실제로 -1인 경우에도 -1을 반환합니다 [Issue 1]. 두 경우 모두 함수가 호출되기 전에 오류 표시기가 이미 설정되어 있었을 수 있고 그러면 오류의 원인을 잘못 짚게 되므로, 이 API는 오류를 일으키기 쉽습니다. 호출 전에 오류를 감지하지 못한 것은 호출하는 코드의 버그이지만, 이 경우 프로그램의 동작만으로는 문제를 찾아내고 디버깅하기가 쉽지 않습니다.

PyObject* 인자를 받으면서, 그 인자가 NULL일 때 특별한 의미를 부여하는 함수들이 있습니다. 예를 들어 PyObject_SetAttr가 설정할 값으로 NULL을 받으면, 이는 해당 속성을 지워야 한다는 뜻입니다. NULL이 값을 만드는 과정에서 발생한 오류를 나타내는데 프로그램이 이 오류를 확인하지 않았을 수도 있으므로, 이 방식은 오류를 일으키기 쉽습니다. 그러면 프로그램은 NULL을 오류가 아닌 다른 의미로 잘못 해석하게 됩니다 [Issue 47].

API 계층과 안정성 보장

API 계층마다 안정성과 API 발전 사이에서, 때로는 성능까지 포함하여 서로 다른 절충점을 제공합니다.

안정 ABI는 검토가 필요한 영역으로 지목되었습니다. 현재 안정 ABI는 불완전하며 널리 채택되지도 않았습니다. 동시에 안정 ABI는 ob_refcnt, ob_type, ob_size 같은 구조체 필드를 노출하기 때문에, 그 존재 자체가 일부 구현 세부 사항을 변경하기 어렵게 만들고 있습니다. 안정 ABI를 유지할 가치가 있는지에 대해서도 논의가 있었습니다. 양측의 인자는 [Issue 4]와 [Issue 9]에서 찾아볼 수 있습니다.

다른 대안으로, 안정 ABI를 발전시킬 수 있으려면 같은 Python 바이너리에서 안정 ABI의 여러 버전을 지원하는 메커니즘이 필요하다는 제안이 있었습니다. 서로 상호 작용하는 함수 그룹을 함께 발전시켜야 할 수도 있으므로, 단일 ABI 버전 안에서 개별 함수를 버전 관리하는 것만으로는 충분하지 않다는 지적이 있었습니다 [Issue 39].

제한된 API는 자주 바뀌지 않을 고품질 API만 사용하고자 하는 사용자에게 권장되는, C API의 공인된 부분 집합으로서 3.2에서 도입되었습니다. Py_LIMITED_API 플래그를 사용하면 프로그램이 제한된 API의 이전 버전만 사용하도록 제한할 수 있지만, 이제는 그 반대로 이전 버전을 제외하는 옵션이 필요합니다. 이렇게 하면 제한된 API에서 결함이 있는 요소를 교체하여 제한된 API를 발전시킬 수 있습니다 [Issue 54]. 더 넓게 보면, 재설계 과정에서 API 계층을 지정하는 방식을 다시 검토하고, 현재 여러 계층 중 하나를 선택하는 방식을 통합할 수 있는 방법을 설계하는 것을 고려해야 합니다 [Issue 59].

이름이 밑줄로 시작하는 API 요소는 비공개로 간주되며, 사실상 안정성 보장이 없는 API 계층에 해당합니다. 하지만 이 점은 최근에야 PEP 689에서 명확해졌습니다. PEP 689 이전부터 있던 이런 API 요소에 대해 어떤 변경 정책을 적용해야 할지는 명확하지 않습니다 [Issue 58].

API 함수 중에는 안전하지 않은(하지만 빠른) 버전과 오류 검사를 수행하는 안전한 버전이 함께 있는 함수들이 있습니다(예를 들어 PyTuple_GET_ITEM 대 PyTuple_GetItem). 이런 함수들을 “안전하지 않은 API” 계층과 “안전한 API” 계층이라는 별도의 계층으로 묶을 수 있다면 도움이 될 수 있습니다 [Issue 61].

C 언어의 사용

CPython이 C 언어를 사용하는 방식과 관련하여 여러 문제가 제기되었습니다. 먼저 어떤 C 방언을 사용하고 그 방언과의 호환성을 어떻게 테스트할지의 문제가 있으며, C++ 방언과의 API 헤더 호환성 문제도 있습니다 [Issue 42].

현재 API에서 const는 드물게 사용되고 있지만, 이를 바꾸는 것을 고려해야 할지는 명확하지 않습니다 [Issue 38].

현재 C 타입 long과 int를 사용하고 있지만, 이제는 int32_t나 int64_t 같은 고정 폭 정수가 더 나은 선택일 수 있습니다 [Issue 27].

매크로, 가변 인자, 열거형, 비트필드, 함수가 아닌 심볼처럼 다른 언어와 상호 작용하기 어려운 C 언어 기능을 사용하고 있습니다 [Issue 35].

PyObject* 인자를 받지만 그 인자가 더 구체적인 타입이어야 하는 API 함수들이 있습니다(예를 들어 PyTuple_Size는 인자가 PyTupleObject*가 아니면 실패합니다). 이것이 바람직한 패턴인지, 아니면 API가 더 구체적인 타입을 받아야 하는지는 아직 결론이 나지 않은 문제입니다 [Issue 31].

API에는 구체적인 타입을 받는 함수들이 있습니다. 예를 들어 PyDict_GetItemString은 PyObject*가 아니라 C 문자열로 지정한 키로 딕셔너리를 조회합니다. 한편 PyDict_ContainsString에 대해서는 구체적인 타입을 받는 대안을 추가하는 것이 적절하지 않다고 봅니다. 이에 관한 원칙을 가이드라인에 문서화해야 합니다 [Issue 23].

구현상의 결함

다음은 국소적인 구현상의 결함 목록입니다. 이들 대부분은 고치기로 결정한다면 아마 점진적으로 고칠 수 있을 것입니다. 어떤 경우든 새로운 API를 설계할 때는 이러한 결함을 피해야 합니다.

성공 시 0을, 실패 시 -1을 반환하는 관례를 따르지 않는 함수들이 있습니다. 예를 들어 PyArg_ParseTuple은 성공 시 0을, 실패 시 0이 아닌 값을 반환합니다 [Issue 25].

Py_CLEAR와 Py_SETREF 매크로는 인자에 두 번 이상 접근하므로, 인자가 부작용이 있는 표현식이면 그 부작용이 중복되어 일어납니다 [Issue 3].

Py_SIZE의 의미는 타입에 따라 달라지며 항상 신뢰할 수 있는 것은 아닙니다 [Issue 10].

일부 API 함수는 대응하는 Python 기능과 동작이 다릅니다. PyIter_Next의 동작은 tp_iternext와 다릅니다. [Issue 29]. PySet_Contains의 동작은 set.__contains__와 다릅니다 [Issue 6].

PyArg_ParseTupleAndKeywords가 const가 아닌 char* 배열을 인자로 받기 때문에 사용하기가 더 어렵습니다 [Issue 28].

Python.h는 API 전체를 노출하지 않습니다. 일부 헤더(marshal.h 등)는 Python.h에 포함되어 있지 않습니다. [Issue 43].

명명

PyLong과 PyUnicode는 자신이 나타내는 Python 타입(int/str)과 더 이상 일치하지 않는 이름을 사용합니다. 이 문제는 새로운 API에서 고칠 수 있습니다 [Issue 14].

API에는 Py/_Py 접두사가 없는 식별자들이 있습니다 [Issue 46].

누락된 기능

이 절은 기능 요청 목록, 즉 현재 C API에 없는 것으로 확인된 기능의 목록입니다.

디버그 모드

다시 컴파일하지 않고도 활성화할 수 있으며, 다양한 유형의 오류를 찾아내는 데 도움이 되는 여러 검사를 켜는 디버그 모드 [Issue 36].

인트로스펙션

현재 C로 정의된 객체에는 Python 객체와 같은 수준의 신뢰할 수 있는 인트로스펙션 기능이 없습니다 [Issue 32].

힙 타입에 대한 효율적인 타입 검사 [Issue 17].

다른 언어와의 상호 작용 개선

다른 GC 기반 언어와의 인터페이스 및 해당 언어의 GC와 Python GC의 통합 [Issue 19].

트레이스백에 외부 스택 프레임 주입 [Issue 18].

다른 언어에서 사용할 수 있는 구체적인 문자열 [Issue 16].

참고 문헌

  1. Python/C API 참조 매뉴얼
  2. 2023 언어 서밋 블로그 게시물: C API에 관한 세 가지 발표
  3. GitHub의 capi-workgroup
  4. C API 워킹 그룹에 관한 Irit의 Core Sprint 2023 슬라이드
  5. Petr의 Core Sprint 2023 슬라이드
  6. HPy에서 배울 점에 관한 HPy 팀의 Core Sprint 2023 슬라이드
  7. Victor의 Core Sprint 2023 Python C API 발표 슬라이드
  8. Python의 안정성 약속 — Cristián Maureira-Fredes, PySide 메인테이너
  9. 5년 전 PySide가 안정 ABI로 전환할 때 겪은 문제에 관한 보고서