PEP 3121 – 확장 모듈 초기화 및 종료
- Author:
- Martin von Löwis <martin at v.loewis.de>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 27-Apr-2007
- Python-Version:
- 3.0
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
현재 확장 모듈 초기화에는 몇 가지 결함이 있습니다. 모듈을 정리하는 기능이 없고, 진입점 이름이 이름 충돌을 일으킬 수 있으며, 진입 함수가 일반적인 호출 규약을 따르지 않고, 여러 인터프리터를 제대로 지원하지 못합니다. 이 PEP는 이러한 문제를 해결합니다.
문제
모듈 종료
현재 확장 모듈은 일반적으로 한 번 초기화된 후 영원히 “살아 있습니다”. 유일한 예외는 Py_Finalize()가 호출되는 경우입니다. 이때 초기화 루틴이 두 번째로 호출됩니다. 이는 리소스 관리 측면에서 좋지 않습니다. 초기화가 호출될 때마다 메모리와 기타 리소스가 할당될 수 있지만, 이를 회수할 방법이 없기 때문입니다. 그 결과 현재 Python이 할당한 모든 리소스를 완전히 해제할 방법이 없습니다.
진입점 이름 충돌
현재 진입점의 이름은 init<module>입니다. 이는 init<something>이라는 이름의 다른 심볼과 충돌할 수 있습니다. 특히 initsocket은 과거에 충돌한 것으로 알려져 있습니다. (이 특정 문제는 모듈 이름을 _socket으로 변경하면서 부수적으로 해결되었습니다.)
진입점 시그니처
현재 진입점은 프로시저(void를 반환하는)입니다. 이는 일반적인 호출 규약에서 벗어납니다. 호출자는 PyErr_Occurred를 확인해야만 초기화 중 오류가 발생했는지 알 수 있습니다. 진입점은 PyObject*를 반환해야 하며, 이는 생성된 모듈이 되거나 예외가 발생한 경우 NULL이 되어야 합니다.
여러 인터프리터
현재 확장 모듈은 모든 인터프리터에서 상태를 공유합니다. 이로 인해 인터프리터 간에 바람직하지 않은 정보 유출이 발생할 수 있습니다. 한 스크립트가 확장 모듈의 객체를 영구적으로 손상시켜 다른 인터프리터의 모든 스크립트를 중단시킬 수도 있습니다.
사양
모듈 초기화 루틴의 시그니처가 다음과 같이 변경됩니다.:
PyObject *PyInit_<modulename>()
초기화 루틴은 모듈을 가져올 때 인터프리터마다 한 번씩 호출됩니다. 초기화 루틴은 호출될 때마다 새 모듈 객체를 반환해야 합니다.
모듈별 상태를 C 변수에 저장하기 위해 각 모듈 객체에는 모듈만 해석하는 메모리 블록이 포함됩니다. 모듈에 사용되는 메모리의 양은 모듈을 생성하는 시점에 지정됩니다.
초기화 함수 외에도 모듈은 여러 추가 콜백 함수를 구현할 수 있습니다. 이러한 함수는 모듈의 tp_traverse, tp_clear 및 tp_free 함수가 호출될 때와 모듈이 다시 로드될 때 호출됩니다.
전체 모듈 정의는 struct PyModuleDef에 결합됩니다.:
struct PyModuleDef{
PyModuleDef_Base m_base; /* To be filled out by the interpreter */
Py_ssize_t m_size; /* Size of per-module data */
PyMethodDef *m_methods;
inquiry m_reload;
traverseproc m_traverse;
inquiry m_clear;
freefunc m_free;
};
모듈 생성 방식이 선택적인 PyModuleDef*를 기대하도록 변경됩니다. 모듈 상태는 null로 초기화됩니다.
각 모듈 메서드에는 모듈 객체가 첫 번째 매개변수로 전달됩니다. 모듈 데이터에 접근하기 위한 함수가:
void* PyModule_GetState(PyObject*);
제공됩니다. 또한 sys.modules를 거치는 것보다 더 효율적으로 모듈을 조회하기 위한 함수가:
PyObject* PyState_FindModule(struct PyModuleDef*);
제공됩니다. 이 조회 함수는 m_base 필드에 있는 인덱스를 사용하여 이름이 아니라 인덱스로 모듈을 찾습니다.
모든 Python 객체는 Python 메모리 관리로 제어해야 하므로, 타입 객체 자체에 메모리로 관리되는 상태가 없는 경우를 제외하고 “static” 타입 객체의 사용은 권장되지 않습니다. 힙 타입의 정의를 간소화하기 위해 새로운 메서드가:
PyTypeObject* PyType_Copy(PyTypeObject*);
추가됩니다.
예제입니다.
xxmodule.c는 initxx 함수를 제거하고 대신 다음 코드를 추가하도록 변경됩니다.:
struct xxstate{
PyObject *ErrorObject;
PyObject *Xxo_Type;
};
#define xxstate(o) ((struct xxstate*)PyModule_GetState(o))
static int xx_traverse(PyObject *m, visitproc v,
void *arg)
{
Py_VISIT(xxstate(m)->ErrorObject);
Py_VISIT(xxstate(m)->Xxo_Type);
return 0;
}
static int xx_clear(PyObject *m)
{
Py_CLEAR(xxstate(m)->ErrorObject);
Py_CLEAR(xxstate(m)->Xxo_Type);
return 0;
}
static struct PyModuleDef xxmodule = {
{}, /* m_base */
sizeof(struct xxstate),
&xx_methods,
0, /* m_reload */
xx_traverse,
xx_clear,
0, /* m_free - not needed, since all is done in m_clear */
}
PyObject*
PyInit_xx()
{
PyObject *res = PyModule_New("xx", &xxmodule);
if (!res) return NULL;
xxstate(res)->ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
if (!xxstate(res)->ErrorObject) {
Py_DECREF(res);
return NULL;
}
xxstate(res)->XxoType = PyType_Copy(&Xxo_Type);
if (!xxstate(res)->Xxo_Type) {
Py_DECREF(res);
return NULL;
}
return res;
}
논의입니다.
Tim Peters는 [1]에서 PythonLabs가 한때 이러한 기능을 고려했다고 보고하며, 현재 이 PEP에서 지원되지 않는 다음과 같은 추가 훅도 열거합니다.
- sys.modules에서 모듈 객체가 삭제될 때입니다.
- Py_Finalize가 호출될 때입니다.
- Python이 종료될 때입니다.
- Python DLL이 언로드될 때입니다(Windows에서만 해당합니다).
참고 문헌입니다.
Copyright
This document has been placed in the public domain.