PEP 820 – PySlot: C API를 위한 통합 슬롯 시스템
- Author:
- Petr Viktorin <encukou at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Created:
- 19-Dec-2025
- Python-Version:
- 3.15
- Post-History:
- 06-Jan-2026
- Resolution:
- 23-Apr-2025
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
타입 슬롯과 모듈 슬롯을 새로운 구조체, 즉 플래그가 있는 태그된 익명 공용체로 대체합니다. 이를 통해 타입 안전성이 향상되고, 새 슬롯을 전방 호환성이 더 좋은 방식으로 추가할 수 있습니다.
3.15에서 추가된 API(PyModule_FromSlotsAndSpec()과 새로운 확장 내보내기 훅)는 새 슬롯을 사용하도록 변경됩니다.
기존 슬롯 구조체와 관련 API는 소프트 폐지(soft-deprecated)됩니다. (즉, 경고 없이 계속 동작하며 완전히 문서화되고 지원되지만, 새로운 기능은 추가하지 않을 계획입니다.)
배경
Python 3.14의 C API에는 새 객체를 생성할 때 정보를 제공하는 데 사용되는 확장 가능한 구조체 두 개가 있습니다. 바로 PyType_Spec과 PyModuleDef입니다.
각 구조체에는 그 구조체로부터 Python 객체를 생성하는 C API 함수 계열이 있습니다. (각 계열은 시간이 지나며 추가된 선택적 인자를 받는 하나의 함수처럼 동작합니다.) 그 계열은 다음과 같습니다:
PyType_Spec에 사용하는PyType_From*함수(예:PyType_FromMetaclass());PyModuleDef에 사용하는PyModule_FromDef*함수(예:PyModule_FromDefAndSpec()).
“입력” 구조체를 런타임 객체와 분리하면 객체의 내부 구조를 (API와 ABI 양쪽에서) 불투명하게 유지할 수 있으므로, 향후 CPython 버전(또는 대체 구현)이 세부 사항을 변경할 수 있습니다.
두 구조체 모두 슬롯 필드를 포함하며, 이 필드는 본질적으로 태그된 공용체 (int ID로 태그된 void 포인터)의 배열입니다. 이를 통해 향후 확장이 가능합니다.
PEP 793에서 새로운 모듈 생성 API가 추가되었습니다. PyModuleDef 구조체 대신 슬롯 배열만 사용합니다. PyModuleDef의 기존 멤버를 대체하기 위해 이에 대응하는 슬롯 ID를 추가합니다. 예를 들어 모듈 이름은 PyModuleDef.m_name이 아니라 Py_mod_name 슬롯에 지정합니다. 해당 PEP에는 다음과 같이 적혀 있습니다:
PyModuleDef_Slot 구조체는 고정 필드에 비해 몇 가지 단점이 있습니다. 저희는 이러한 단점을 고칠 수 있다고 생각하지만, 이 PEP의 범위에서는 제외합니다.
이 제안은 그 단점들을 해결합니다.
동기
기존 PyModuleDef_Slot과 PyType_Slot의 주요 단점은 다음과 같습니다:
- 타입 안전성
void *는 데이터 포인터, 함수 포인터, 작은 정수에 두루 사용되므로 캐스팅이 필요합니다. 이러한 캐스팅은 관련된 모든 아키텍처에서 실제로는 동작하지만, 엄밀히 말하면 C에서 정의되지 않은 동작이거나 구현 정의 동작입니다.예를 들어
Py_tp_doc매크로는 문자열을,Py_mod_gil매크로는 작은 정수를,Py_tp_repr매크로는 함수를 나타내며, 모두void*로 캐스트해야 합니다.- 제한적인 전방 호환성
- 확장이 현재 인터프리터가 알지 못하는 슬롯 ID를 제공하면 타입/모듈 생성이 실패합니다. 이 때문에 “선택적” 기능, 즉 인터프리터가 지원하는 경우에만 효과를 내야 하는 기능을 사용하기가 번거롭습니다. 최근에 추가된 슬롯인
Py_mod_gil매크로와Py_mod_multiple_interpreters매크로가 좋은 예입니다.한 가지 우회 방법은 Python 버전을 확인하고, 현재 인터프리터보다 먼저 나온 슬롯을 생략하는 것입니다. 이는 사용자에게 번거로운 일입니다. 또한 이는 CPython이 아닌 C API 구현의 가능성을 제약하여, 이러한 구현이 더 새로운 CPython 버전에서 도입된 기능을 “골라서 가져오는(cherry-picking)” 것을 막습니다.
지지 의견
이 PEP는 최종 사용자에게 즉시 도움이 되지 않는다는 점에서 특이합니다. 이 PEP는 도움이 되기 시작하기 훨씬 전부터 마련되어 있어야 합니다. Cython 유지 관리자 Da Woods의 말을 빌리면 다음과 같습니다:
Cython이 이것을 즉시 사용하지는 않을 것 같습니다(적어도 클래스에 대해서는 그렇습니다… 물론 이것이 PEP 793에 포함된다면 그 부분에는 사용할 것입니다). 단지 이것이 즉각적인 새 기능보다는 주로 향후 개선을 목표로 하기 때문입니다. 그래도 사용할 만해 보입니다.
대신, 지지는 C API를 변경해야 하고 이를 하위 호환성을 유지하는 방식으로 하기를 원하는 코어 개발자들로부터 나옵니다:
- Mark Shannon:가치 있는 개선으로 보입니다. 저를 +1로 세어 주십시오.
- Victor Stinner:저는 생각을 바꾸었고 이제 PEP 820을 지지합니다 :) 시간이 좀 걸렸지만 이제 PEP 820의 장점이 보입니다: 이 PEP는 하위 호환성과 안정 ABI를 향상시킵니다.
예시
이 제안은 슬롯 배열로부터 클래스와 모듈을 생성하는 API를 추가하며, 슬롯 배열은 다음과 같이 매크로를 사용하여 C 리터럴로 지정할 수 있습니다:
static PySlot myClass_slots[] = {
PySlot_STATIC_DATA(tp_name, "mymod.MyClass"),
PySlot_SIZE(tp_extra_basicsize, sizeof(struct myClass)),
PySlot_FUNC(tp_repr, myClass_repr),
PySlot_INT64(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_MANAGED_DICT),
PySlot_END,
}
// ...
PyObject *MyClass = PyType_FromSlots(myClass_slots);
매크로는 손으로 작성하는 리터럴을 단순하게 만들어 줍니다. 여러 Python 버전 간의 호환성이나 템플릿화되거나 자동 생성된 슬롯 배열과 같은 더 복잡한 사용 사례, 그리고 C가 아닌 언어에서 C API를 사용하는 사용자의 경우에는 슬롯 구조체 정의를 풀어서 작성할 수 있습니다. 예를 들어, nb_matrix_multiply 슬롯(PEP 465)이 3.5가 아니라 가까운 미래(예컨대 CPython 3.17)에 추가되었다면, 사용자는 “OPTIONAL” 플래그와 함께 이 슬롯을 추가하여 해당 연산자가 있는 CPython 버전에서만 클래스가 @ 연산자를 지원하도록 할 수 있습니다:
static PySlot myClass_slots[] = {
...
{ // skipped if not supported
.sl_id=Py_nb_matrix_multiply,
.sl_flags=PySlot_OPTIONAL,
.sl_func=myClass_matmul,
},
PySlot_END,
}
근거
여기서는 이 제안의 설계 결정을 설명합니다.
근거의 일부는 PyModuleDef 구조체를 슬롯 배열로 대체한 PEP 793에서 되풀이한 것입니다.
슬롯 사용
슬롯의 주된 대안은 입력으로 버전이 지정된 struct를 사용하는 것입니다.
이러한 설계에는 두 가지 변형이 있습니다:
- 모든 정보를 위한 필드를 갖춘 큰 구조체입니다.
PyTypeObject에서 볼 수 있듯이, 실제로는 이러한 구조체의 대부분이 NULL인 경향이 있습니다. 더 많은 필드가 쓸모없어짐에 따라, 낭비가 커지거나, 아니면 (한동안 이전 레이아웃과의 호환성을 유지하면서) 새로운 구조체 레이아웃을 도입하게 됩니다. - 초기 생성에 필요한 정보만 담고, 다른 정보는 나중에 (전용 함수 호출이나 Python 수준의
setattr로) 추가하는 작은 구조체입니다. 이 설계는:- 필요한 정보를 추가/폐기/조정하는 것을 번거롭게 만듭니다(예를 들어, PEP 697에서 저는 기존 필드의 음수 값에 의미를 부여했습니다. 비슷한 상황에서는 새 필드를 추가하는 편이 더 깔끔할 것입니다);
- 확장 모듈과 인터프리터 사이의 API 호출 횟수를 늘립니다.
“라이브” 객체를 수정하는 API와 일부 중복되더라도, 타입/모듈 생성을 위한 “일괄(batch)” API는 타당하다고 생각합니다.
슬롯만 사용하기
PyType_Spec 클래스와 PyModuleDef 클래스에는 슬롯 배열 외에 명시적인 필드가 있습니다. 여기에는 다음이 포함됩니다:
- 클래스 이름(
PyType_Spec.name)과 같은 필수 정보. 이 제안은 이름을 위한 슬롯 ID를 추가하고, 이를 필수로 만듭니다. - 포인터가 아닌 값(
basicsize,flags). 원래 슬롯은 함수 포인터만 담도록 의도되었으나, 이제는 정수나 플래그뿐 아니라 데이터 포인터도 담습니다. 이 제안은 공용체(union)를 사용하여 타입을 깔끔하게 처리합니다. - 슬롯 메커니즘 이전에 추가된 항목.
PyModuleDef.m_slots자체는 항상 NULL이었던m_reload의 용도를 바꾼 것이며, 선택적인m_traverse나m_methods멤버는 이보다 먼저 존재했습니다.
이러한 필드 없이 오직 슬롯 배열만 두어도 됩니다. 배열을 감싸는 래퍼 클래스를 두면 설계가 복잡해집니다. 그러한 클래스의 필드가 더 이상 쓸모없어지면, 이를 제거하거나 다른 용도로 바꾸기가 어렵습니다.
중첩된 슬롯 테이블
이 제안에서 슬롯 배열은 다른 슬롯 배열을 참조할 수 있으며, 참조된 배열은 재귀적으로 “부모” 배열에 병합된 것처럼 취급됩니다. 이는 인터프리터 내부의 슬롯 처리를 복잡하게 만들지만, 다음을 가능하게 합니다:
- 동적으로 할당된(또는 스택에 할당된) 슬롯을
static슬롯과 섞어 쓰기. 이는PyType_From*계열 함수가 일반적으로static일 수 없는 값을 받도록 계속 늘어나게 된 문제를 해결합니다. 예를 들어,PyType_FromModuleAndSpec()의 module 인자는 힙에 할당된 모듈 객체여야 합니다. - 여러 클래스/모듈에 공통된 기능을 구현하기 위해 슬롯의 일부를 공유하기.
- 일부 슬롯을 조건부로(예: Python 버전에 따라) 쉽게 포함하기.
중첩된 “레거시” 슬롯 테이블
PyType_Slot의 중첩 배열과 마찬가지로, “새” 슬롯 안에 “레거시” 슬롯(PyType_Slot 및 PyModuleDef_Slot) 배열을 넣는 것과 그 반대의 경우도 지원할 것을 제안합니다.
이렇게 하면 사용자는 이미 작성한 코드를 다시 작성하거나 형식을 바꾸지 않고 재사용할 수 있으며, 새 기능이 필요한 경우에만 “새” 슬롯을 사용하면 됩니다.
고정 너비 정수
이 제안은 슬롯 ID(uint16_t)와 플래그(uint64_t)에 고정 너비 정수를 사용합니다. C int 타입에서 16비트를 넘게 사용하면 이식성이 없지만, 일반적인 플랫폼에서는 아무 경고 없이 동작합니다. int를 사용하면서 UINT16_MAX를 넘는 값을 피하면 일반적인 플랫폼에서 16비트를 낭비하게 됩니다.
메모리 레이아웃
일반적인 64비트 플랫폼에서는 새 구조체의 크기를 기존 PyType_Slot 및 PyModuleDef_Slot과 같게 유지할 수 있습니다. (기존 구조체는 int 이식성과 패딩 때문에 16바이트 중 6바이트를 낭비합니다. 이 제안은 그 비트 중 일부를 새 기능에 활용합니다.) 32비트 플랫폼에서는 이 제안이 64비트와 같은 레이아웃을 요구하므로, 기존 구조체에 비해 크기가 두 배가 됩니다(8바이트에서 16바이트로). 보통 static인 “설정” 데이터에는 문제가 없을 것입니다.
이 제안은 비트 필드와 열거형을 사용하지 않습니다. 이들의 메모리 표현은 컴파일러에 따라 달라지므로, C 이외의 언어에서 API를 사용할 때 문제를 일으키기 때문입니다.
이 구조체는 타입의 정렬이 그 크기와 일치한다고 가정하고 배치됩니다.
단일 ID 공간
현재 모듈 슬롯과 타입 슬롯의 숫자 값은 서로 겹칩니다:
Py_bf_getbuffer==Py_mod_create== 1Py_bf_releasebuffer==Py_mod_exec== 2Py_mp_ass_subscript==Py_mod_multiple_interpreters== 3Py_mp_length==Py_mod_gil== 4- CPython 3.15에서 추가된 모듈 슬롯도 마찬가지입니다.
이 제안은 두 가지 모두에 하나의 번호 체계를 사용하므로, 앞으로 추가될 슬롯은 이런 겹침을 피합니다. 그 목적은 다음과 같습니다:
- 타입 슬롯을 모듈에 실수로 사용하거나 그 반대로 사용하는 일을 방지합니다.
- 외부 라이브러리나 검사기가 ID를 바탕으로 슬롯의 의미(와 타입)를 판별할 수 있게 합니다.
기존의 겹침 4개 때문에 지금 당장은 이 목표를 달성하지 못하지만, 사용자가 알아차리지 못하는 방식으로 새 숫자 ID로 점진적으로 이전할 수 있습니다.
주된 단점은 내부 조회 테이블이 더 커지거나(타입과 모듈에 별도의 테이블을 사용하면 빈 칸이 생기므로), 관리하기 더 어려워진다는(테이블을 합치는 경우) 점입니다.
폐지 경고
여러 슬롯이 NULL 값을 허용하지 않는다고 문서화되어 있지만, CPython은 하위 호환성을 위해 NULL을 허용합니다. 마찬가지로 여러 슬롯 ID는 한 배열에 두 번 이상 나타나서는 안 되지만, CPython은 이런 중복을 허용합니다.
이는 유지 보수상의 문제입니다. 구현이 바뀌더라도 CPython은 이런 경우에 문서화되지 않은(그리고 대개 테스트되지 않은) 동작을 유지해야 하기 때문입니다.
또한 이는 API 확장을 가로막습니다. 예를 들어 3.10에서 Py_TPFLAGS_DISALLOW_INSTANTIATION 매크로 플래그를 추가하는 대신, Py_tp_new 슬롯을 NULL로 설정하는 것을 허용하여 같은 효과를 낼 수도 있었습니다.
(먼) 미래에 경계 사례의 동작을 바꿀 수 있도록, 그리고 C API의 대체 구현이 가능하다면 자유롭게 만들어질 수 있도록, 이런 경우에 런타임 폐지 경고를 발생시키기 시작합니다. 사용자가 통제할 수 없는 일로 경고가 쏟아지지 않도록, 폐지 경고는 새 API를 사용할 때만 표시합니다.
명세
새 PySlot 구조체는 다음과 같이 정의됩니다.:
typedef struct PySlot {
uint16_t sl_id;
uint16_t sl_flags;
union {
uint32_t _sl_reserved; // must be 0
};
union {
void *sl_ptr;
void (*sl_func)(void);
Py_ssize_t sl_size;
int64_t sl_int64;
uint64_t sl_uint64;
};
} PySlot;
sl_id: 슬롯이 하는 일을 식별하는 슬롯 번호입니다.sl_flags: 아래에 정의된 플래그입니다.- 향후 확장을 위해 예약된 32비트입니다(향후 플래그로 활성화될 것으로 예상됩니다).
- 데이터를 담는 공용체이며, 그 타입은 슬롯에 따라 달라집니다.
새 API
다음 함수가 추가됩니다. 주어진 슬롯 배열로부터 해당하는 Python 타입 객체를 생성합니다:
PyObject *PyType_FromSlots(const PySlot *slots);
이 함수에서는 Py_tp_token 슬롯을 Py_TP_USE_SPEC(즉, NULL)로 설정할 수 없습니다.
변경된 API
PyModule_FromSlotsAndSpec 함수(PEP 793에 따라 CPython 3.15에 추가됨)는 새 슬롯 구조체를 받도록 변경됩니다.:
PyObject *PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec)
PEP 793에서 추가된 확장 모듈 내보내기 훅(PyModExport_<name>)은 새 슬롯 구조체를 반환하도록 변경됩니다. PyMODEXPORT_FUNC 매크로도 이에 맞게 갱신됩니다.
일반적인 슬롯 의미 체계
슬롯을 적용하는 함수에 슬롯이 전달될 때, 그 함수는 슬롯 배열은 물론 배열이 (재귀적으로) 가리키는 어떤 데이터도 수정하지 않습니다.
함수가 완료된 후에는 사용자가 배열과 배열이 (재귀적으로) 가리키는 모든 데이터를 수정하거나 할당 해제할 수 있습니다. 단, 명시적으로 “정적”이라고 표시된 경우는 예외입니다(아래 PySlot_STATIC 참조). 즉, 인터프리터는 일반적으로 char * 텍스트를 포함하여 구조체의 모든 데이터를 복사해야 합니다.
플래그
sl_flags에는 다음 비트를 설정할 수 있습니다. 할당되지 않은 비트는 0으로 설정해야 합니다.
PySlot_OPTIONAL: 슬롯 ID를 알 수 없으면 인터프리터는 해당 슬롯을 완전히 무시해야 합니다. (예를 들어, 지금nb_matrix_multiply가 CPython에 추가되고 있다면 여러분의 타입에서 이 플래그를 사용할 수 있습니다.)PySlot_STATIC: 슬롯이 가리키는 모든 데이터가 정적으로 할당되어 있으며 상수입니다. 따라서 인터프리터는 해당 정보를 복사할 필요가 없습니다. 함수 포인터에는 이 플래그가 암묵적으로 적용됩니다.이 플래그는 슬롯이 “간접적으로” 가리키는 데이터에도 적용됩니다. 단, 중첩된 슬롯(아래 중첩된 슬롯 테이블 참조)은 예외이며, 중첩된 슬롯은 자체
PySlot_STATIC플래그를 가질 수 있습니다. 예를 들어,PyMemberDef구조체의 배열을 가리키는Py_tp_members슬롯에 적용된 경우, 배열 전체뿐만 아니라 그 요소에 있는name및doc문자열도 정적이고 상수여야 합니다.정적 데이터가 필요한 슬롯(
Py_mod_methods,Py_tp_methods,Py_tp_members,Py_tp_getset)에는 이 플래그가 필수가 됩니다.PySlot_INTPTR: 데이터가sl_ptr에 저장되며, 적절한 타입으로 캐스트해야 합니다.이 플래그는 모든 슬롯이 이러한 방식으로 동작하는 기존
PyType_Slot및PyModuleDef_Slot에서 이식하는 작업을 단순화합니다.
편의 매크로
슬롯 정의를 단순화하기 위해 다음 매크로가 API에 추가됩니다.:
#define PySlot_DATA(NAME, VALUE) \
{.sl_id=NAME, .sl_ptr=(void*)(VALUE)}
#define PySlot_FUNC(NAME, VALUE) \
{.sl_id=NAME, .sl_func=(VALUE)}
#define PySlot_SIZE(NAME, VALUE) \
{.sl_id=NAME, .sl_size=(VALUE)}
#define PySlot_INT64(NAME, VALUE) \
{.sl_id=NAME, .sl_int64=(VALUE)}
#define PySlot_UINT64(NAME, VALUE) \
{.sl_id=NAME, .sl_uint64=(VALUE)}
#define PySlot_STATIC_DATA(NAME, VALUE) \
{.sl_id=NAME, .sl_flags=PySlot_STATIC, .sl_ptr=(VALUE)}
#define PySlot_END {0}
또한 C++11 호환 코드에서 사용할 수 있도록 명명된 초기화자(named initializer)를 사용하지 않는 매크로 두 개를 추가합니다. 이 매크로들은 값을 void*로 캐스트하므로 기존 슬롯에 비해 타입 안전성을 개선하지 않는다는 점에 유의하십시오.:
#define PySlot_PTR(NAME, VALUE) \
{NAME, PySlot_INTPTR, {0}, {(void*)(VALUE)}}
#define PySlot_PTR_STATIC(NAME, VALUE) \
{NAME, PySlot_INTPTR|Py_SLOT_STATIC, {0}, {(void*)(VALUE)}}
중첩된 슬롯 테이블
슬롯 테이블을 중첩할 수 있도록 새 슬롯 Py_slot_subslots가 추가됩니다. 그 값(sl_ptr)은 PySlot 구조체의 배열을 가리켜야 하며, 이 배열은 현재 슬롯 배열의 일부인 것처럼 취급됩니다. 슬롯이 없음을 나타내려면 sl_ptr을 NULL로 설정할 수 있습니다.
기존 슬롯 구조체에 대해서도 비슷한 중첩을 허용하는 슬롯 두 개가 추가됩니다.
PyType_Slot배열을 위한Py_tp_slotsPyModuleDef_Slot배열에는Py_mod_slots
배열의 각 PyType_Slot은 (PySlot){.sl_id=slot, .sl_flags=PySlot_INTPTR, .sl_ptr=func}로 변환되며(이를 필요로 하는 슬롯에는 sl_flags에 PySlot_STATIC이 추가됩니다), PyModuleDef_Slot도 마찬가지로 변환됩니다.
초기 구현에서는 중첩 깊이가 5단계로 제한됩니다. 이 제한은 향후 해제될 수 있습니다.
새 슬롯 ID
타입 정의와 모듈 정의 모두에 사용할 수 있는 다음 새 슬롯 ID가 추가됩니다:
Py_slot_end(0으로 정의됨): 슬롯 배열의 끝을 표시합니다.PySlot_INTPTR및PySlot_STATIC플래그는 무시됩니다.PySlot_OPTIONAL플래그는Py_slot_end와 함께 사용할 수 없습니다.
Py_slot_subslots,Py_tp_slots,Py_mod_slots: 위의 중첩된 슬롯 테이블를 참조하십시오.Py_slot_invalid(UINT16_MAX, 즉-1로 정의됨): 알 수 없는 슬롯 ID로 취급됩니다.
PyModuleDef의 기존 멤버를 다루기 위해 다음 새 슬롯 ID가 추가됩니다:
Py_tp_name(타입 생성 시 필수)Py_tp_basicsize(Py_ssize_t타입)Py_tp_extra_basicsize(PyType_Spec.basicsize를-extra_basicsize로 설정하는 것과 동일)Py_tp_itemsizePy_tp_flags
PyType_FromMetaclass의 인자를 다루기 위해 다음 새 슬롯 ID가 추가됩니다:
Py_tp_metaclass(메타클래스 계산 후ob_type을 설정하는 데 사용됨)Py_tp_module
Py_tp_base와 Py_tp_bases는 이미 존재한다는 점에 유의하십시오. 인터프리터는 이 둘을 동일하게 취급합니다. 둘 중 어느 것으로든 클래스 객체 하나 또는 클래스 객체의 튜플을 지정할 수 있습니다. Py_tp_base는 Py_tp_bases를 권장하는 방향으로 소프트 폐지(soft-deprecated)됩니다. 하나의 정의에서 둘을 모두 지정하는 것은 폐지 예정(deprecated)이 됩니다(현재는 Py_tp_bases가 Py_tp_base를 덮어씁니다).
새 슬롯은 어느 것도 PyType_GetSlot과 함께 사용할 수 없습니다. (이 제한은 향후 C API WG의 승인을 받아 해제될 수 있습니다.)
새 슬롯 중 Py_slot_end, Py_slot_subslots, Py_tp_slots, Py_mod_slots만 PyType_Spec 및/또는 PyModuleDef에서 허용됩니다.
슬롯 번호 재지정
새 슬롯 ID는 고유한 숫자 값을 갖습니다(즉, Py_slot_*, Py_tp_*, Py_mod_*는 ID를 공유하지 않습니다).
1번부터 4번까지 번호가 매겨진 슬롯(Py_bf_getbuffer…Py_mp_length 및 Py_mod_create…Py_mod_gil)은 새로운 (더 큰) 번호로 재정의됩니다. 기존 번호는 별칭으로 남으며, 3.15 미만의 안정 ABI 버전을 대상으로 컴파일할 때 사용됩니다.
PEP 793에서 추가된 PyModuleDef 멤버용 슬롯은 고유한 ID를 갖도록 번호가 다시 매겨집니다:
Py_mod_namePy_mod_docPy_mod_state_sizePy_mod_methodsPy_mod_state_traversePy_mod_state_clearPy_mod_state_free
소프트 폐지
다음 기존 함수들은 소프트 폐지 대상이 됩니다:
PyType_FromSpecPyType_FromSpecWithBasesPyType_FromModuleAndSpecPyType_FromMetaclassPyModule_FromDefAndSpecPyModule_FromDefAndSpec2PyModule_ExecDef
(다시 짚어 두면, 소프트 폐지된 API는 제거가 예정되어 있지 않고, 경고를 발생시키지 않으며, 계속 문서화되고 테스트됩니다. 다만 새로운 기능은 더 이상 추가되지 않습니다.)
이 함수들이 받는 PyType_Slot 또는 PyModuleDef_Slot 배열에는 이 PEP에서 정의한 “새” 슬롯을 포함하여 어떤 슬롯이든 들어갈 수 있습니다. 여기에는 중첩된 “새 스타일” 슬롯(Py_slot_subslots)도 포함됩니다.
폐지 경고
PySlot 배열을 받는 함수(더 오래된 PyType_Slot 또는 PyModuleDef_Slot 배열을 받는 함수는 제외)는 다음 경우에 런타임 폐지 경고를 발생시킵니다. 이는 해당 경우가 현재 문서에서는 허용되지 않지만 런타임에서는 허용되는 슬롯에 적용됩니다:
- 슬롯 값을 NULL로 설정하는 경우:
Py_tp_doc을 제외한 모든 타입 슬롯Py_mod_createPy_mod_exec
- 하나의 슬롯 배열(이 PEP에서 추가하는 하위 슬롯 배열 포함) 안에서 같은 슬롯 ID를 반복하는 경우:
- 이미 런타임 오류가 발생하는 슬롯(
Py_tp_doc,Py_tp_members)을 제외한 모든 타입 슬롯 Py_mod_createPy_mod_abi
- 이미 런타임 오류가 발생하는 슬롯(
하위 호환성
이 PEP는 Python 3.15 알파 버전에서 이미 릴리스된 API를 변경할 것을 제안합니다. 이로 인해 해당 API를 일찍 도입한 사용자는 불편을 겪게 되지만, 첫 베타 이전에 이 PEP가 수락되고 구현되기만 한다면 이 변경은 하위 호환성 정책의 문구와 취지에 부합합니다.
슬롯 번호 재지정은 하위 호환성을 유지하는 방식으로 이루어집니다. 기존 값도 계속 허용되며, 이전 안정 ABI를 대상으로 컴파일할 때 사용됩니다.
허용되지 않는다고 문서화된 일부 경우에는 폐지 예정 경고가 발생하기 시작합니다(폐지 경고 참조).
그 외에 이 PEP는 API를 추가하고 소프트 폐지(soft-deprecate)하기만 하므로 하위 호환성이 유지됩니다.
보안 영향
알려진 바 없습니다
교육 방법
“Extending and Embedding” 튜토리얼이 이 API를 사용하도록 수정합니다.
참조 구현
이 PEP가 수락된 후, 구현이 CPython issue 149044에서 병합되었습니다.
기각된 아이디어
여러 대안 아이디어는 근거 절을 참조하십시오.
서드파티 슬롯 ID 할당
서드파티가 자체 용도로 슬롯 ID를 예약할 수 있도록 허용하자는 제안이 있었습니다. 이는 주로 대체 구현에 유용할 것입니다. 예를 들어, GraalPy 같은 구현은 사용자 정의 타입 슬롯(예: “이 Java 클래스를 상속함” 슬롯)을 원할 수 있습니다. 마찬가지로, PyPy는 한때 typeobject 구조체에 tp_pypy_flags라는 추가 필드를 두었습니다.
이 PEP는 네임스페이스 메커니즘을 규정하지 않습니다. 이는 향후 추가될 수 있습니다. 또한 대체 구현을 위해 개별 슬롯 ID를 자유롭게 예약할 수도 있습니다.
특정 객체를 생성하는 데 사용된 슬롯을 조회하는 API가 없으므로, 슬롯은 확장 모듈이 타입이나 모듈에 추가 데이터를 덧붙이는 좋은 방법이 아니라는 점에 유의하십시오.
익명 공용체 사용 회피
이 PEP는 익명 공용체를 포함하는 구조체를 제안하는데, 익명 공용체는 아직 CPython의 문서화된 공개 API에서 사용되지 않습니다.
이를 추가하는 데 알려진 문제는 없지만, 다음 사항이 관련될 수 있습니다:
- 익명 공용체는 C에서는 C11부터만 지원됩니다. 하지만 CPython은 이미 이 기능을 요구하고 있으며,
PyObject구조체의 내부 멤버에 이를 사용합니다. - C 스타일 지정 초기화자(designated initializer)를 추가한 C++20 이전까지는, C++ 초기화자로 공용체의 첫 번째 멤버만 설정할 수 있습니다. 그러나 이는 이름 있는 공용체에서도 마찬가지로 문제가 됩니다. 공용체를 완전히 피한다면 이 PEP가 제공하는 타입 안전성 개선의 대부분을 잃게 됩니다.
제안된 플래그
PySlot_INTPTR및 우회용 매크로PySlot_PTR,PySlot_PTR_STATIC을 사용하면, C++11과 호환되어야 하거나 이와 비슷한 공용체 관련 제약이 있는 코드에서도 이 API를 사용할 수 있다는 점에 유의하십시오. - C++에는 익명 구조체가 없습니다. 익명 구조체와 익명 공용체를 하나의 언어 기능으로 여기는 C 프로그래머에게는 이 점이 의외일 수 있습니다.
- C/C++가 아닌 언어의 래퍼는 이 공용체(union)에 이름을 붙여야 할 수도 있습니다. 그렇게 해도 괜찮습니다. (독자 여러분께: 이것이 필요하다면 헤더와 문서에 권장 이름을 노출하는 것에 관한 CPython 이슈를 열어 주십시오.)
더 큰 그림에서 보면, 익명 공용체는 태그된 공용체(tagged union)를 구현하고 공개 API를 하위 호환성을 유지하는 방식으로 발전시키는 데 유용한 도구가 될 수 있습니다. 이 PEP는 익명 공용체를 더 자주 사용할 수 있는 길을 의도적으로 열어 둡니다.
대체 슬롯
이 PEP의 이전 버전에서는 PySlot_HAS_FALLBACK 플래그를 제안했습니다:
플래그가 지정된 슬롯의 ID를 알 수 없으면 인터프리터는 해당 슬롯을 무시합니다. ID를 알고 있으면 인터프리터는 HAS_FALLBACK이 없는 첫 번째 슬롯까지(해당 슬롯 포함) 뒤따르는 슬롯을 무시해야 합니다.사실상 HAS_FALLBACK 플래그가 있는 연속된 슬롯들과 그 뒤에 오는 HAS_FALLBACK이 없는 첫 번째 슬롯이 하나의 “블록”을 이루며, 인터프리터는 블록 안에서 자신이 이해하는 첫 번째 슬롯만 고려합니다. 블록 전체를 선택 사항으로 만들려면 블록이 OPTIONAL 플래그가 있는 슬롯으로 끝나야 합니다.
이 플래그는 필요해지는 Python 버전에서 나중에 추가될 수 있습니다. (하위 호환성을 위해 HAS_FALLBACK 플래그가 지정된 모든 슬롯에는 OPTIONAL 플래그도 지정해야 합니다.)
미해결 문제
아직 없습니다.
감사의 글
이번 제안 개정에 많은 의견을 주신 Da Woods, Antoine Pitrou, Mark Shannon, Victor Stinner께 감사드립니다.
변경 이력
- 17-Jun-2026
- 정적 데이터가 필요한 슬롯에는
PySlot_STATIC플래그를 명시적으로 지정하도록 요구합니다. - PEP가 최종(final)으로 표시되었습니다.
- 정적 데이터가 필요한 슬롯에는
- 24-Apr-2026
- NULL 슬롯과 반복된 슬롯에 대한 폐지 예정(deprecation)을 새 API로만 한정합니다.
- PEP가 수락되었습니다.
- 12-Mar-2026 - 불필요한
PySlot_HAS_FALLBACK플래그를 제거합니다. - 28-Jan-2026
- 3.15 알파에서 추가된 PEP 793 API(
PyModExport,PyModule_FromSlotsAndSpec)가 새 슬롯을 반환하도록 변경된다는 점을 더 명확히 합니다. - 동작하지 않는다고 문서화되어 있었지만 실제로는 동작하던 것들을 폐지 예정으로 지정합니다:
- 슬롯 값을 NULL로 설정하는 것(해당 슬롯이 이를 명시적으로 허용하는 경우는 제외)
- 정의에서 슬롯 ID를 반복하는 것(다음은 제외: )
- 거부된 아이디어에 “서드파티 슬롯 ID 할당”과 “익명 공용체 회피”를 추가합니다.
- 3.15 알파에서 추가된 PEP 793 API(
- 06-Jan-2025 - 최초 PEP
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.