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

Python 개선 제안 한국어 번역

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 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Important

This PEP is a historical document. The up-to-date, canonical documentation can now be found at Isolating Extension Modules HOWTO.

×

See PEP 1 for how to propose changes.

초록

전통적으로 Python 확장 모듈에 속하는 상태는 프로세스 전체 범위를 갖는 C static 변수에 보관되었습니다. 이 문서는 이러한 프로세스별 상태의 문제점과 모듈별 상태를 더 나은 기본값으로서 가능하고 쉽게 사용할 수 있도록 하려는 노력을 설명합니다.

또한 가능한 경우 모듈별 상태로 전환하는 방법을 설명합니다. 이 전환에는 해당 상태를 위한 공간 할당, 정적 타입에서 힙 타입으로 전환하는 작업, 그리고 아마도 가장 중요하게는 코드에서 모듈별 상태에 액세스하는 작업이 포함됩니다.

이 문서에 관하여

정보 제공 informational PEP로서, 이 문서는 어떠한 변경도 도입하지 않습니다. 그러한 변경은 각자의 PEP에서(충분히 작다면 이슈에서) 이루어져야 합니다. 대신 여러 릴리스에 걸쳐 진행되는 노력의 동기를 다루고, 완성된 기능을 사용하는 방법을 초기 도입자에게 안내합니다.

지원이 충분히 완성되면 이 내용은 Python 문서의 HOWTO로 옮길 수 있습니다. 그동안 문서 주도 개발의 정신에 따라 이 PEP에서 식별된 공백은 노력의 초점을 어디에 맞출지 보여 줄 수 있으며, 새로운 기능이 구현됨에 따라 업데이트할 수 있습니다.

이 PEP에서 확장 모듈을 언급할 때마다 해당 조언은 내장 모듈에도 적용됩니다.

Note

이 PEP에는 일반적인 조언이 포함되어 있습니다. 이를 따를 때는 항상 프로젝트의 구체적인 사항을 고려하십시오.

예를 들어 이 조언의 상당 부분은 Python 표준 라이브러리의 C 부분에 적용되지만, 이 PEP는 stdlib의 세부 사항(특이한 하위 호환성 문제, 비공개 API에 대한 액세스 등)을 고려하지 않습니다.

이 노력과 관련된 PEP는 다음과 같습니다.

  • PEP 384안정적인 ABI 정의하기는 힙 타입을 생성하기 위한 C API를 추가했습니다.
  • PEP 489다단계 확장 모듈 초기화
  • PEP 573C 확장 메서드에서 모듈 상태에 액세스하기

이 문서는 모든 Python 구현에서 제공되는 것은 아닌 Python의 공개 C 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 489PEP 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 참조). 그러나 이 원칙을 따르는 코드보다 엣지 케이스에 대해 더 많은 고민과 주의가 필요합니다.

일부 모듈에는 덜 엄격한 제한이 더 적합할 수 있지만, 격리된 모듈을 사용하면 다양한 사용 사례에 적용되는 명확한 기대 사항(및 지침)을 설정하기가 더 쉽습니다.

놀라운 예외 사례

격리된 모듈은 몇 가지 놀라운 예외 사례를 만들어 낸다는 점에 유의하십시오. 특히 각 모듈 객체는 일반적으로 다른 유사한 모듈과 클래스 및 예외를 공유하지 않습니다. 위의 example above에서 이어서, old_binascii.Errorbinascii.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에 도입된 multi-phase extension module initialization을 사용하십시오. 이는 모듈이 여러 인터프리터를 올바르게 지원한다는 것을 나타냅니다.

모듈에 로컬인 저장 공간으로 해당 바이트 수를 요청하려면 PyModuleDef.m_size를 양수로 설정하십시오. 일반적으로 이는 모든 모듈의 C 수준 상태를 저장할 수 있는 모듈별 struct의 크기로 설정됩니다. 특히 C 코드가 작동하는 데 필요한 클래스에 대한 포인터(예외를 포함하되 정적 타입은 제외)와 설정(예: csvfield_size_limit)을 이곳에 배치해야 합니다.

Note

또 다른 방법은 모듈의 __dict__에 상태를 저장하는 것이지만, 사용자가 Python 코드에서 __dict__를 수정하더라도 충돌하지 않도록 해야 합니다. 이는 C 수준에서 오류 검사 및 타입 검사를 수행해야 한다는 뜻이며, 이를 잘못 구현하기 쉽고 충분히 테스트하기도 어렵습니다.

모듈 상태에 PyObject 포인터가 포함되어 있다면, 모듈 객체는 해당 객체에 대한 참조를 보유하고 모듈 수준 훅인 m_traverse, m_clearm_free를 구현해야 합니다. 이는 클래스의 tp_traverse, tp_cleartp_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 코드에 정의된 타입은 static입니다. 즉, 코드에 직접 정의되고 PyType_Ready()를 사용하여 초기화되는 static PyTypeObject 구조체입니다.

이러한 타입은 필연적으로 프로세스 전체에서 공유됩니다. 모듈 객체 간에 이러한 타입을 공유하려면 타입이 소유하거나 액세스하는 모든 상태에 주의를 기울여야 합니다. 발생할 수 있는 문제를 제한하기 위해 정적 타입은 Python 수준에서 변경할 수 없습니다. 예를 들어 str.myattribute = 123을 설정할 수 없습니다.

Note

실제로 변경할 수 없는 객체를 여러 인터프리터 간에 공유하는 것은 해당 객체가 변경 가능한 객체에 대한 액세스를 제공하지 않는 한 문제가 없습니다. 그러나 CPython에서는 모든 Python 객체에 변경 가능한 구현 세부 사항인 참조 횟수가 있습니다. 참조 횟수의 변경은 GIL로 보호됩니다. 따라서 인터프리터 간에 Python 객체를 공유하는 코드는 암묵적으로 CPython의 현재 프로세스 전체 GIL에 의존합니다.

정적 타입은 변경할 수 없고 프로세스 전역이므로 “자신의” 모듈 상태에 액세스할 수 없습니다. 이러한 타입의 메서드가 모듈 상태에 액세스해야 한다면, 해당 타입을 heap-allocated type, 즉 줄여서 heap type으로 변환해야 합니다. 이는 Python의 class문으로 생성되는 클래스에 더 가깝게 대응합니다.

새 모듈에서는 기본적으로 힙 타입을 사용하는 것이 좋은 경험칙입니다.

정적 타입을 힙 타입으로 변환할 수 있지만, 힙 타입 API는 정적 타입을 “lossless”하게 변환하도록, 즉 주어진 정적 타입과 정확히 동일하게 작동하는 타입을 생성하도록 설계되지 않았다는 점에 유의하십시오. 정적 타입과 달리 힙 타입 객체는 기본적으로 변경할 수 있습니다. 또한 새 API에서 클래스 정의를 다시 작성할 때 몇 가지 세부 사항(예: 피클 가능성 또는 상속된 슬롯)을 의도치 않게 변경할 가능성이 높습니다. 자신에게 중요한 세부 사항은 항상 테스트하십시오.

힙 타입 정의

PyType_Spec구조체에 클래스의 설명 또는 “blueprint”를 채우고 PyType_FromModuleAndSpec()를 호출하여 새 클래스 객체를 구성함으로써 힙 타입을 생성할 수 있습니다.

Note

PyType_FromSpec()와 같은 다른 함수도 힙 타입을 생성할 수 있지만, PyType_FromModuleAndSpec()는 모듈을 클래스와 연결하므로 메서드에서 모듈 상태에 액세스할 수 있습니다.

클래스는 일반적으로 C에서 안전하게 접근할 수 있도록 모듈 상태와 Python 코드에서 접근할 수 있도록 모듈의 __dict__ 모두에 저장해야 합니다.

가비지 컬렉션 프로토콜

힙 타입의 인스턴스는 해당 타입에 대한 참조를 보유합니다. 이를 통해 모든 인스턴스가 소멸되기 전에 타입이 소멸되지 않도록 보장하지만, 가비지 컬렉터가 해제해야 하는 참조 순환이 발생할 수 있습니다.

메모리 누수를 방지하려면 힙 타입의 인스턴스는 가비지 컬렉션 프로토콜을 구현해야 합니다. 즉, 힙 타입은 다음을 충족해야 합니다.

  • Py_TPFLAGS_HAVE_GC 플래그를 가져야 합니다.
  • 타입을 방문하는 Py_tp_traverse를 사용하여 traverse 함수를 정의해야 합니다(예: Py_VISIT(Py_TYPE(self)); 사용).

추가로 고려할 사항은 Py_TPFLAGS_HAVE_GCtp_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_newtp_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_classtype(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 구현으로, 예를 들어 nb_add__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_GetModuleByDefMRO (즉, 모든 슈퍼클래스)를 검색하여 해당 모듈을 가진 첫 번째 슈퍼클래스를 찾는 방식으로 작동합니다.

Note

매우 특이한 경우(같은 정의로부터 생성된 여러 모듈에 걸친 상속 체인)에는 PyType_GetModuleByDef가 실제 정의 클래스의 모듈을 반환하지 않을 수도 있습니다. 그러나 이것은 항상 같은 정의를 가진 모듈을 반환하여, 호환되는 C 메모리 레이아웃을 보장합니다.

모듈 상태의 수명

모듈 객체가 가비지 컬렉션될 때, 해당 모듈 상태는 해제됩니다. 모듈 상태(의 일부)를 가리키는 각 포인터에 대해, 모듈 객체에 대한 참조를 유지해야 합니다.

보통 이것은 문제가 되지 않는데, PyType_FromModuleAndSpec으로 생성된 타입과 그 인스턴스들이 모듈에 대한 참조를 유지하기 때문입니다. 그러나 외부 라이브러리의 콜백과 같은 다른 곳에서 모듈 상태를 참조할 때는 참조 카운팅에 주의해야 합니다.

미해결 문제

모듈별 상태와 힙 타입을 둘러싼 몇 가지 문제가 아직 미해결로 남아 있습니다.

이 상황을 개선하는 방법에 대한 논의는 capi-sig mailing list에서 하는 것이 가장 좋습니다.

타입 검사

현재(Python 3.10 기준) 힙 타입에는 Py*_Check 함수를 작성할 수 있는 좋은 API가 없어서(정적 타입인 strPyUnicode_Check가 존재하는 것처럼), 인스턴스가 특정 C 레이아웃을 가지도록 보장하기가 쉽지 않습니다.

메타클래스

현재(Python 3.10 기준) 힙 타입의 메타클래스, 즉 타입 객체의 ob_type 필드를 지정할 수 있는 좋은 API가 없습니다.

클래스별 스코프

타입에 상태를 부착하는 것도 불가능합니다. PyHeapTypeObject는 가변 크기 객체(PyVarObject)이지만, 현재 그 가변 크기 저장 공간은 슬롯이 소비하고 있습니다. 이를 해결하는 것은 상속 계층 구조 내 여러 클래스가 일부 상태를 예약해야 할 수도 있다는 사실 때문에 복잡해집니다.

힙 타입으로의 무손실 변환

힙 타입 API는 정적 타입으로부터의 “무손실” 변환, 즉 주어진 정적 타입과 정확히 동일하게 동작하는 타입을 생성하기 위해 설계되지 않았습니다. 이를 해결하는 가장 좋은 방법은 아마도 알려진 “함정”들을 다루는 가이드를 작성하는 것일 것입니다.