PEP 630 – 확장 모듈 격리하기
- Author:
- Petr Viktorin <encukou at gmail.com>
- Discussions-To:
- Capi-SIG list
- Status:
- Final
- Type:
- Informational
- Created:
- 25-Aug-2020
- Post-History:
- 16-Jul-2020
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
전통적으로 Python 확장 모듈에 속한 상태는 프로세스 전체 범위를 가지는 C static 변수에 보관되었습니다. 이 문서는 이러한 프로세스별 상태의 문제점을 설명하고, 더 나은 기본값인 모듈별 상태를 가능하게 하고 사용하기 쉽게 만들기 위한 노력을 설명합니다.
또한 이 문서는 가능한 경우 모듈별 상태로 전환하는 방법도 설명합니다. 이 전환에는 해당 상태를 위한 공간 할당, 경우에 따라 정적 타입에서 힙 타입으로의 전환, 그리고 아마도 가장 중요한 것으로 코드에서 모듈별 상태에 접근하는 일이 포함됩니다.
이 문서에 대하여
정보성 PEP로서, 이 문서는 어떠한 변경 사항도 도입하지 않습니다. 변경 사항은 각각의 PEP(또는 충분히 작다면 이슈)에서 이루어져야 합니다. 대신 이 문서는 여러 릴리스에 걸친 노력의 동기를 다루고, 초기 도입자에게 완성된 기능을 사용하는 방법을 안내합니다.
지원이 어느 정도 완성되면, 이 내용은 파이썬 문서의 HOWTO로 옮겨질 수 있습니다. 그동안에는 문서 주도 개발의 정신에 따라, 이 PEP에서 확인된 공백이 노력을 집중할 곳을 보여 줄 수 있으며, 새로운 기능이 구현됨에 따라 이 PEP도 갱신될 수 있습니다.
이 PEP에서 확장 모듈을 언급할 때마다, 그 조언은 내장 모듈에도 적용됩니다.
Note
이 PEP는 일반적인 조언을 담고 있습니다. 이 조언을 따를 때는 항상 여러분 프로젝트의 특수성을 고려하십시오.
예를 들어, 이 조언의 상당 부분이 Python 표준 라이브러리의 C 부분에도 적용되지만, 이 PEP는 표준 라이브러리 고유의 사항(특이한 하위 호환성 문제, 비공개 API 접근 등)은 고려하지 않습니다.
이 노력과 관련된 PEP는 다음과 같습니다:
- PEP 384 – 안정 ABI 정의로, 힙 타입을 생성하기 위한 C API를 추가했습니다.
- PEP 489 – 다단계 확장 모듈 초기화
- PEP 573 – C 확장 메서드에서의 모듈 상태 접근
이 문서는 Python의 공개 C API를 다루는데, 모든 Python 구현이 이 API를 제공하지는 않습니다. 그러나 이 PEP의 어떤 내용도 CPython에만 국한되지 않습니다.
다른 모든 정보 제공용 PEP와 마찬가지로, 이 글이 반드시 Python 커뮤니티의 합의나 권고를 나타내는 것은 아닙니다.
동기
인터프리터는 Python 코드가 실행되는 컨텍스트입니다. 인터프리터는 구성(예: 임포트 경로)과 런타임 상태(예: 임포트된 모듈의 집합)를 포함합니다.
Python은 한 프로세스에서 여러 인터프리터를 실행하는 것을 지원합니다. 고려해야 할 경우는 두 가지입니다. 사용자는 인터프리터를 다음과 같이 실행할 수 있습니다:
Py_InitializeEx/Py_FinalizeEx주기를 여러 번 거치며 순차적으로 실행하거나,Py_NewInterpreter/Py_EndInterpreter를 사용해 “서브 인터프리터”를 관리하며 병렬로 실행할 수 있습니다.
두 경우 모두(그리고 이들의 조합도) 라이브러리 안에 Python을 내장할 때 가장 유용합니다. 라이브러리는 일반적으로 자신을 사용하는 애플리케이션에 대해 가정을 해서는 안 되며, 여기에는 프로세스 전체에 걸친 “주 Python 인터프리터”를 가정하는 것도 포함됩니다.
현재 CPython은 이러한 사용 사례를 잘 처리하지 못합니다. C static 변수는 사용하기가 매우 쉽기 때문에 많은 확장 모듈(심지어 일부 표준 라이브러리 모듈까지도)이 프로세스별 전역 상태를 사용합니다. 그 결과, 인터프리터마다 따로 있어야 할 데이터가 여러 인터프리터 사이에서 공유됩니다. 확장 개발자가 주의하지 않으면, 같은 프로세스에서 모듈이 둘 이상의 인터프리터에 로드될 때 크래시로 이어지는 경계 사례가 매우 쉽게 생깁니다.
안타깝게도 인터프리터별 상태는 달성하기 쉽지 않습니다—확장 작성자는 개발할 때 여러 인터프리터를 염두에 두지 않는 경향이 있고, 현재로서는 그 동작을 테스트하기도 번거롭습니다.
모듈별 상태의 근거
Python의 C API는 인터프리터별 상태에 집중하는 대신, 더 세분화된 모듈별 상태를 더 잘 지원하는 방향으로 발전하고 있습니다. 기본적으로 C 수준의 데이터는 모듈 객체에 연결됩니다. 그러면 각 인터프리터가 자신만의 모듈 객체를 생성하므로 데이터가 분리된 상태로 유지됩니다. 격리를 테스트하기 위해 하나의 확장에 대응하는 여러 모듈 객체를 단일 인터프리터에 로드할 수도 있습니다.
모듈별 상태를 사용하면 수명과 자원 소유권을 쉽게 파악할 수 있습니다. 확장 모듈은 모듈 객체가 생성될 때 초기화되고, 모듈 객체가 해제될 때 정리됩니다. 이런 점에서 모듈은 다른 PyObject *와 똑같습니다. 신경 써야 할(또는 잊어버릴 수 있는) “인터프리터 종료 시” 훅이 없습니다.
목표: 사용하기 쉬운 모듈 상태
현재는 모듈을 격리된 상태로 유지하면서 C API가 제공하는 모든 기능을 사용하기가 번거롭거나 불가능합니다. PEP 384로 가능해진 PEP 489와 PEP 573의 변경 사항(그리고 앞으로 계획된 변경 사항)은 먼저 이런 방식으로 모듈을 만드는 것을 가능하게 하고, 이어서 이 방식으로 새 모듈을 작성하고 기존 모듈을 변환하는 것을 쉽게 만들어, 이 방식이 자연스러운 기본값이 되도록 하는 것을 목표로 합니다.
모듈별 상태가 기본값이 되더라도 프로세스별, 인터프리터별, 스레드별, 태스크별 상태처럼 다른 수준의 캡슐화가 필요한 사용 사례는 존재할 것입니다. 목표는 이러한 경우를 예외적인 사례로 다루는 것입니다. 이런 상태도 사용할 수 있어야 하지만, 확장 작성자는 이에 대해 더 신중하게 고려해야 합니다.
목표가 아닌 것: 속도 향상과 GIL
GIL을 인터프리터별로 만들어 멀티 코어 CPU에서 CPython의 속도를 높이려는 노력이 진행되고 있습니다. 인터프리터를 격리하는 것이 그 노력에 도움이 되기는 하지만, 모듈별 상태를 기본으로 삼으면 여러 인터프리터 지원이 기본적으로 더 안전해지므로 속도 향상을 얻지 못하더라도 이점이 있습니다.
여러 인터프리터에서 모듈을 안전하게 만들기
확장 모듈에서 여러 인터프리터를 올바르게 지원하는 방법은 많습니다. 이 글의 나머지 부분에서는 그러한 모듈을 작성하거나 기존 모듈을 변환할 때 권장하는 방법을 설명합니다.
지원 작업은 아직 진행 중이라는 점에 유의하십시오. 모듈에 필요한 일부 기능의 API가 아직 준비되지 않았을 수 있습니다.
전체 예제 모듈은 xxlimited에서 볼 수 있습니다.
이 절에서는 “여러분”이 확장 모듈 작성자라고 가정합니다.
격리된 모듈 객체
확장 모듈을 개발할 때 명심해야 할 핵심은 하나의 공유 라이브러리에서 여러 모듈 객체가 생성될 수 있다는 점입니다. 예를 들면 다음과 같습니다:
>>> import sys
>>> import binascii
>>> old_binascii = binascii
>>> del sys.modules['binascii']
>>> import binascii # create a new module object
>>> old_binascii == binascii
False
경험 법칙으로, 두 모듈은 완전히 독립적이어야 합니다. 모듈에 특정한 모든 객체와 상태는 모듈 객체 안에 캡슐화되어야 하고, 다른 모듈 객체와 공유되지 않아야 하며, 모듈 객체가 할당 해제될 때 정리되어야 합니다. 예외도 있을 수 있지만(Managing Global State 참조), 이 경험 법칙을 따르는 코드보다 더 많이 고민하고 경계 사례에 더 주의를 기울여야 합니다.
일부 모듈은 덜 엄격한 제한으로도 충분할 수 있지만, 격리된 모듈을 사용하면 다양한 사용 사례에 두루 적용되는 명확한 기대 사항(과 지침)을 정하기가 더 쉬워집니다.
예상치 못한 예외 상황
격리된 모듈은 몇 가지 예상치 못한 예외 상황을 만든다는 점에 유의하십시오. 특히, 각 모듈 객체는 일반적으로 자신의 클래스와 예외를 다른 유사한 모듈과 공유하지 않습니다. 위의 예제에 이어서, old_binascii.Error와 binascii.Error는 별개의 객체라는 점에 유의하십시오. 다음 코드에서 예외는 잡히지 않습니다:
>>> old_binascii.Error == binascii.Error
False
>>> try:
... old_binascii.unhexlify(b'qwertyuiop')
... except binascii.Error:
... print('boo')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
binascii.Error: Non-hexadecimal digit found
이는 예상된 동작입니다. 순수 Python 모듈도 같은 방식으로 동작한다는 점에 주목하십시오. 이는 Python이 동작하는 방식의 일부입니다.
목표는 확장 모듈을 C 수준에서 안전하게 만드는 것이지, 해킹이 직관적으로 동작하게 만드는 것이 아닙니다. sys.modules를 “수동으로” 변경하는 것은 해킹으로 간주됩니다.
전역 상태 관리하기
때로는 Python 모듈의 상태가 해당 모듈에 국한되지 않고 프로세스 전체(또는 모듈보다 “더 전역적인” 다른 무언가)에 해당하기도 합니다. 예를 들면 다음과 같습니다:
readline모듈은 단 하나뿐인 터미널을 관리합니다.- 회로 기판에서 실행되는 모듈은 단 하나뿐인 온보드 LED를 제어하려 합니다.
이러한 경우 Python 모듈은 전역 상태를 소유하기보다는 그 상태에 대한 접근을 제공해야 합니다. 가능하다면 모듈의 여러 사본이 (Python용이든 다른 언어용이든 다른 라이브러리와 함께) 그 상태에 독립적으로 접근할 수 있도록 모듈을 작성하십시오.
그것이 불가능하다면 명시적인 잠금을 고려하십시오.
프로세스 전역 상태를 사용해야 한다면, 여러 인터프리터로 인한 문제를 피하는 가장 간단한 방법은 모듈이 프로세스당 두 번 이상 로드되지 않도록 명시적으로 막는 것입니다. Opt-Out: Limiting to One Module Object per Process를 참조하십시오.
모듈별 상태 관리하기
모듈별 상태를 사용하려면 PEP 489에서 도입된 다단계 확장 모듈 초기화를 사용하십시오. 이는 모듈이 여러 인터프리터를 올바르게 지원한다는 것을 알립니다.
PyModuleDef.m_size를 양수로 설정하여 그만큼의 바이트를 모듈 로컬 저장 공간으로 요청하십시오. 일반적으로 이 값은 모듈의 C 수준 상태를 모두 저장할 수 있는 모듈 고유의 어떤 struct의 크기로 설정됩니다. 특히 이곳은 C 코드의 함수가 동작하는 데 필요한 클래스(예외는 포함하되 정적 타입은 제외)에 대한 포인터와 설정(예: csv의 field_size_limit)을 넣어야 하는 곳입니다.
Note
또 다른 방법은 상태를 모듈의 __dict__에 저장하는 것이지만, 사용자가 Python 코드에서 __dict__를 수정할 때 크래시가 발생하지 않도록 해야 합니다. 이는 C 수준에서 오류 검사와 타입 검사를 해야 한다는 뜻인데, 이는 잘못 구현하기 쉽고 충분히 테스트하기 어렵습니다.
모듈 상태에 PyObject 포인터가 포함되어 있다면, 모듈 객체는 해당 객체들에 대한 참조를 보유해야 하며 모듈 수준 훅인 m_traverse, m_clear, m_free를 구현해야 합니다. 이들은 클래스의 tp_traverse, tp_clear, tp_free처럼 동작합니다. 이를 추가하려면 약간의 작업이 필요하고 코드가 길어집니다. 이는 깔끔하게 언로드될 수 있는 모듈을 위해 치러야 하는 대가입니다.
모듈별 상태를 갖는 모듈의 예제는 현재 xxlimited로 제공되며, 예제 모듈 초기화는 파일 하단에 나와 있습니다.
옵트아웃: 프로세스당 하나의 모듈 객체로 제한하기
음수가 아닌 PyModuleDef.m_size는 모듈이 여러 인터프리터를 올바르게 지원한다는 신호입니다. 여러분의 모듈이 아직 그렇지 않다면, 모듈을 프로세스당 한 번만 로드할 수 있도록 명시적으로 만들 수 있습니다. 예를 들면 다음과 같습니다:
static int loaded = 0;
static int
exec_module(PyObject* module)
{
if (loaded) {
PyErr_SetString(PyExc_ImportError,
"cannot load module more than once per process");
return -1;
}
loaded = 1;
// ... rest of initialization
}
함수에서 모듈 상태에 접근하기
모듈 수준 함수에서 상태에 접근하는 것은 간단합니다. 함수는 첫 번째 인자로 모듈 객체를 받습니다. 상태를 추출하려면 PyModule_GetState를 사용할 수 있습니다:
static PyObject *
func(PyObject *module, PyObject *args)
{
my_struct *state = (my_struct*)PyModule_GetState(module);
if (state == NULL) {
return NULL;
}
// ... rest of logic
}
Note
모듈 상태가 없는 경우, 즉 PyModuleDef.m_size가 0인 경우 PyModule_GetState는 예외를 설정하지 않고 NULL을 반환할 수 있습니다. 여러분 자신의 모듈에서는 m_size를 직접 제어하므로 이를 쉽게 방지할 수 있습니다.
힙 타입
전통적으로 C 코드에서 정의된 타입은 정적입니다. 즉, 코드에 직접 정의되고 PyType_Ready()로 초기화되는 static PyTypeObject 구조체입니다.
이러한 타입은 필연적으로 프로세스 전체에서 공유됩니다. 이러한 타입을 모듈 객체 사이에서 공유하려면 그 타입이 소유하거나 접근하는 모든 상태에 주의를 기울여야 합니다. 발생할 수 있는 문제를 제한하기 위해, 정적 타입은 Python 수준에서 불변입니다. 예를 들어 str.myattribute = 123처럼 설정할 수 없습니다.
Note
진정으로 불변인 객체를 인터프리터 간에 공유하는 것은 그 객체가 가변 객체에 대한 접근을 제공하지 않는 한 괜찮습니다. 하지만 CPython에서는 모든 Python 객체에 변경 가능한 구현 세부 사항이 하나 있는데, 바로 참조 횟수입니다. 참조 횟수의 변경은 GIL에 의해 보호됩니다. 따라서 인터프리터 간에 Python 객체를 공유하는 코드는 무엇이든 CPython의 현재 프로세스 전역 GIL에 암묵적으로 의존합니다.
정적 타입은 불변이며 프로세스 전역이기 때문에 “자신의” 모듈 상태에 접근할 수 없습니다. 이러한 타입의 메서드 중 하나라도 모듈 상태에 접근해야 한다면, 그 타입은 힙 할당 타입, 줄여서 힙 타입으로 변환되어야 합니다. 이들은 Python의 class 문으로 만든 클래스에 더 가깝게 대응합니다.
새 모듈에서는 기본적으로 힙 타입을 사용하는 것이 좋은 경험 법칙입니다.
정적 타입을 힙 타입으로 변환할 수는 있지만, 힙 타입 API는 정적 타입으로부터의 “무손실” 변환, 즉 주어진 정적 타입과 정확히 똑같이 동작하는 타입을 만드는 것을 목적으로 설계되지 않았다는 점에 유의하십시오. 정적 타입과 달리 힙 타입 객체는 기본적으로 가변입니다. 또한 새 API로 클래스 정의를 다시 작성할 때 의도치 않게 몇 가지 세부 사항(예: 피클 가능 여부나 상속된 슬롯)을 바꾸게 될 가능성이 높습니다. 여러분에게 중요한 세부 사항은 항상 테스트하십시오.
힙 타입 정의하기
힙 타입은 클래스의 설명 또는 “청사진”인 PyType_Spec 구조체를 채우고, PyType_FromModuleAndSpec()을 호출하여 새 클래스 객체를 구성함으로써 생성할 수 있습니다.
Note
PyType_FromSpec() 같은 다른 함수로도 힙 타입을 생성할 수 있지만, PyType_FromModuleAndSpec()은 모듈을 클래스와 연결하여 메서드에서 모듈 상태에 접근할 수 있게 해 줍니다.
클래스는 일반적으로 모듈 상태(C에서 안전하게 접근하기 위해)와 모듈의 __dict__(Python 코드에서 접근하기 위해) 둘 다에 저장해야 합니다.
가비지 컬렉션 프로토콜
힙 타입의 인스턴스는 자신의 타입에 대한 참조를 가집니다. 이는 모든 인스턴스가 파괴되기 전에 타입이 파괴되지 않도록 보장하지만, 가비지 컬렉터가 끊어 주어야 하는 참조 순환이 생길 수 있습니다.
메모리 누수를 방지하려면 힙 타입의 인스턴스는 가비지 컬렉션 프로토콜을 구현해야 합니다. 즉, 힙 타입은 다음을 충족해야 합니다:
Py_TPFLAGS_HAVE_GC플래그를 가져야 합니다.Py_tp_traverse를 사용하여 타입을 방문하는 traverse 함수를 정의해야 합니다(예:Py_VISIT(Py_TYPE(self));를 사용).
추가로 고려할 사항은 Py_TPFLAGS_HAVE_GC 및 tp_traverse의 문서를 참조하십시오.
traverse 함수가 베이스 클래스(또는 다른 타입)의 tp_traverse에 처리를 위임한다면, Py_TYPE(self)가 한 번만 방문되도록 하십시오. tp_traverse에서 타입을 방문해야 하는 것은 힙 타입뿐이라는 점에 유의하십시오.
예를 들어, traverse 함수에 다음이 포함되어 있고:
base->tp_traverse(self, visit, arg)
…그리고 base가 정적 타입일 수 있다면, 다음도 포함해야 합니다:
if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
// a heap type's tp_traverse already visited Py_TYPE(self)
} else {
Py_VISIT(Py_TYPE(self));
}
tp_new와 tp_clear에서 타입의 참조 횟수를 처리할 필요는 없습니다.
클래스에서 모듈 상태에 접근하기
PyType_FromModuleAndSpec()으로 정의된 타입 객체가 있다면, PyType_GetModule을 호출하여 연관된 모듈을 얻은 다음 PyModule_GetState를 호출하여 모듈의 상태를 얻을 수 있습니다.
번거로운 오류 처리 상용구 코드를 줄이기 위해 PyType_GetModuleState를 사용하여 이 두 단계를 결합할 수 있으며, 그 결과는 다음과 같습니다:
my_struct *state = (my_struct*)PyType_GetModuleState(type);
if (state === NULL) {
return NULL;
}
일반 메서드에서 모듈 상태에 접근하기
클래스의 메서드에서 모듈 수준 상태에 접근하는 것은 다소 더 복잡하지만, PEP 573에서 도입된 변경 사항 덕분에 가능합니다. 상태를 얻으려면 먼저 정의 클래스를 얻은 다음, 그 클래스에서 모듈 상태를 얻어야 합니다.
가장 큰 걸림돌은 메서드가 정의된 클래스, 줄여서 그 메서드의 “정의 클래스”를 얻는 것입니다. 정의 클래스는 자신이 속한 모듈에 대한 참조를 가질 수 있습니다.
정의 클래스를 Py_TYPE(self)와 혼동하지 마십시오. 메서드가 여러분 타입의 서브클래스에서 호출되면 Py_TYPE(self)는 그 서브클래스를 가리키며, 이 서브클래스는 여러분의 모듈과 다른 모듈에 정의되어 있을 수 있습니다.
Note
다음 Python 코드로 이 개념을 설명할 수 있습니다. Base.get_defining_class는 type(self) == Sub인 경우에도 Base를 반환합니다:
class Base:
def get_defining_class(self):
return __class__
class Sub(Base):
pass
메서드가 자신의 “정의 클래스”를 얻으려면 METH_METHOD | METH_FASTCALL | METH_KEYWORDS 호출 규약과 이에 대응하는 PyCMethod 시그니처를 사용해야 합니다:
PyObject *PyCMethod(
PyObject *self, // object the method was called on
PyTypeObject *defining_class, // defining class
PyObject *const *args, // C array of arguments
Py_ssize_t nargs, // length of "args"
PyObject *kwnames) // NULL, or dict of keyword arguments
정의 클래스를 얻었다면 PyType_GetModuleState를 호출하여 연관된 모듈의 상태를 얻으십시오.
예를 들면 다음과 같습니다:
static PyObject *
example_method(PyObject *self,
PyTypeObject *defining_class,
PyObject *const *args,
Py_ssize_t nargs,
PyObject *kwnames)
{
my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);
if (state === NULL) {
return NULL;
}
... // rest of logic
}
PyDoc_STRVAR(example_method_doc, "...");
static PyMethodDef my_methods[] = {
{"example_method",
(PyCFunction)(void(*)(void))example_method,
METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
example_method_doc}
{NULL},
}
슬롯 메서드, 게터 및 세터에서 모듈 상태에 접근하기
Note
이 기능은 Python 3.11에서 새로 추가되었습니다.
슬롯 메서드(특수 메서드에 대응하는 빠른 C 구현으로, 예를 들어 __add__에 대한 nb_add나 초기화에 대한 tp_new 등)는 PyCMethod와 달리 정의 클래스를 전달받을 수 없는 매우 단순한 API를 가지고 있습니다. PyGetSetDef로 정의된 게터와 세터도 마찬가지입니다.
이러한 경우 모듈 상태에 접근하려면 PyType_GetModuleByDef 함수를 사용하고 모듈 정의를 전달하십시오. 모듈을 얻었다면 PyModule_GetState를 호출하여 상태를 얻으십시오:
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
my_struct *state = (my_struct*)PyModule_GetState(module);
if (state === NULL) {
return NULL;
}
PyType_GetModuleByDef는 MRO (즉, 모든 슈퍼클래스)를 검색하여 대응하는 모듈을 가진 첫 번째 슈퍼클래스를 찾는 방식으로 동작합니다.
Note
매우 특이한 경우(같은 정의로 생성된 여러 모듈에 걸친 상속 체인)에는 PyType_GetModuleByDef가 실제로 정의한 클래스의 모듈을 반환하지 않을 수도 있습니다. 하지만 항상 같은 정의를 가진 모듈을 반환하므로 호환되는 C 메모리 레이아웃이 보장됩니다.
모듈 상태의 수명
모듈 객체가 가비지 수집되면 그 모듈 상태는 해제됩니다. 모듈 상태(또는 그 일부)를 가리키는 포인터마다 모듈 객체에 대한 참조를 유지해야 합니다.
PyType_FromModuleAndSpec으로 생성된 타입과 그 인스턴스는 모듈에 대한 참조를 유지하므로 보통은 이것이 문제가 되지 않습니다. 하지만 외부 라이브러리용 콜백처럼 다른 곳에서 모듈 상태를 참조할 때는 참조 카운팅에 주의해야 합니다.
미해결 문제
모듈별 상태와 힙 타입을 둘러싼 몇 가지 문제가 아직 해결되지 않았습니다.
이 상황을 개선하기 위한 논의는 capi-sig 메일링 리스트에서 하는 것이 가장 좋습니다.
타입 검사
현재(Python 3.10 기준) 힙 타입에는 Py*_Check 함수(정적 타입인 str에 대해 PyUnicode_Check가 존재하는 것과 같은 함수)를 작성할 좋은 API가 없으므로, 인스턴스가 특정 C 레이아웃을 갖는지 보장하기가 쉽지 않습니다.
메타클래스
현재(Python 3.10 기준) 힙 타입의 메타클래스, 즉 타입 객체의 ob_type 필드를 지정할 좋은 API가 없습니다.
클래스별 스코프
타입에 상태를 붙이는 것도 불가능합니다. PyHeapTypeObject는 가변 크기 객체(PyVarObject)이지만, 그 가변 크기 저장 공간은 현재 슬롯이 사용하고 있습니다. 상속 계층 구조에 속한 여러 클래스가 각각 어떤 상태를 예약해야 할 수도 있기 때문에 이 문제를 해결하기는 까다롭습니다.
힙 타입으로의 무손실 변환
힙 타입 API는 정적 타입으로부터의 “무손실” 변환, 즉 주어진 정적 타입과 정확히 똑같이 동작하는 타입을 만드는 것을 염두에 두고 설계되지 않았습니다. 이를 해결하는 가장 좋은 방법은 아마도 알려진 “함정”을 다루는 가이드를 작성하는 것입니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.