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 함수가 호출될 때와 모듈이 다시 로드될 때 호출됩니다.
전체 모듈 정의는 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 메모리 관리를 통해 제어되어야 하므로, 타입 객체 자체에 메모리 관리 대상 상태가 없는 경우가 아니라면 “정적” 타입 객체의 사용은 권장되지 않습니다. 힙 타입 정의를 단순화하기 위해 다음과 같은 새 메서드가:
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.