확장형 정의하기: 자습서¶
파이썬은 C 확장 모듈 작성자가 내장 str과 list 형과 마찬가지로 파이썬 코드에서 조작할 수 있는 새로운 형을 정의할 수 있도록 합니다. 모든 확장형의 코드는 패턴을 따르지만, 시작하기 전에 이해해야 할 세부 사항이 있습니다. 이 설명서는 주제에 대한 간단한 소개입니다.
기초¶
The CPython runtime sees all Python objects as variables of type
PyObject*, which serves as a “base type” for all Python objects.
The PyObject structure itself only contains the object’s
reference count and a pointer to the object’s “type object”.
This is where the action is; the type object determines which (C) functions
get called by the interpreter when, for instance, an attribute gets looked up
on an object, a method called, or it is multiplied by another object. These
C functions are called “type methods”.
따라서, 새 확장형을 정의하려면, 새 형 객체를 만들어야 합니다.
이런 개념은 예시를 통해 설명하는 것이 가장 좋으므로, C 확장 모듈 custom 내에 Custom 이라는 새로운 타입을 정의하는 최소한의 완전한 모듈을 소개합니다.
참고
여기에 표시하는 것은 정적인(static) 확장형을 정의하는 전통적인 방법입니다. 대부분의 용도에 적합해야 합니다. C API는 또한 PyType_FromSpec() 함수를 사용하여 힙 할당 확장형을 정의 할 수 있습니다만, 이 자습서에서는 다루지 않습니다.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
} CustomObject;
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
// Just use this while using static types
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_doc = "Example module that creates an extension type.",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom(void)
{
return PyModuleDef_Init(&custom_module);
}
이제는 한 번에 배워야 할 것이 많지만, 이전 장과 비슷해 보이기를 바랍니다. 이 파일은 세 가지를 정의합니다:
Custom객체 가 포함하는 내용: 이것은CustomObject구조체이며, 각Custom인스턴스마다 한 번씩 할당됩니다.Custom형(type) 이 동작하는 방식: 이것은CustomType구조체로, 특정 작업이 요청될 때 인터프리터가 확인하는 일련의 플래그와 함수 포인터를 정의합니다.custom모듈을 정의하고 실행하는 방법: 이는 모듈을 정의하기 위한PyInit_custom함수와 관련된custom_module구조체, 그리고 새로운 모듈 객체를 설정하는custom_module_exec함수입니다.
첫 번째 것은:
typedef struct {
PyObject_HEAD
} CustomObject;
이것은 Custom 객체가 포함할 내용입니다. PyObject_HEAD 는 모든 객체 구조의 시작 부분에 필수적이며, 타입 객체에 대한 포인터와 참조 횟수를 포함하는 ob_base 라는 이름의 PyObject 필드를 정의합니다(이들은 각각 Py_TYPE 및 Py_REFCNT 매크로를 사용하여 접근할 수 있습니다). 매크로를 사용하는 이유는 레이아웃을 추상화하고 디버그 빌드 에서 추가적인 필드를 지원하기 위함입니다.
참고
PyObject_HEAD 매크로 뒤에는 세미콜론이 없습니다. 실수로 추가하는 것에 주의하십시오: 일부 컴파일러는 불평할 것입니다.
물론, 객체는 일반적으로 표준 PyObject_HEAD 관용구 외에 추가 데이터를 저장합니다; 예를 들어, 표준 파이썬 floats에 대한 정의는 다음과 같습니다:
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
두 번째 것은 형 객체의 정의입니다.
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
};
참고
신경 쓰지 않는 모든 PyTypeObject 필드를 나열하지 않고 필드의 선언 순서를 신경 쓰지 않으려면, 위와 같이 C99 스타일의 지명(designated) 초기화자를 사용하는 것이 좋습니다.
object.h에 있는 PyTypeObject의 실제 정의는 위의 정의보다 더 많은 필드를 갖습니다. 나머지 필드는 C 컴파일러에 의해 0으로 채워지며, 필요하지 않으면 명시적으로 지정하지 않는 것이 일반적입니다.
한 번에 한 필드씩 따로 다루려고 합니다:
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
이 줄은 위에서 언급한 ob_base 필드를 초기화하기 위한 필수 상용구입니다.
.tp_name = "custom.Custom",
우리 형의 이름. 이것은 객체의 기본 텍스트 표현과 일부 에러 메시지에 나타납니다, 예를 들어:
>>> "" + custom.Custom()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "custom.Custom") to str
Note that the name is a dotted name that includes both the module name and the
name of the type within the module. The module in this case is custom and
the type is Custom, so we set the type name to custom.Custom.
Using the real dotted import path is important to make your type compatible
with the pydoc and pickle modules.
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
이것은 파이썬이 새로운 Custom 인스턴스를 생성할 때 얼마나 많은 메모리를 할당해야 하는지 알게 하기 위함입니다. tp_itemsize 는 가변 크기 객체에만 사용되며, 그 외의 경우에는 0이어야 합니다.
참고
파이썬에서 해당 타입을 서브클래스화 가능하게 하려 할 때, 당신의 타입이 기본형과 동일한 tp_basicsize 를 가지면 다중 상속 시 문제가 발생할 수 있습니다. 이 경우 파이썬에서 만든 서브클래스는 __bases__ 에서 귀하의 타입을 가장 먼저 나열해야 하며, 그렇지 않으면 귀하의 타입의 __new__() 메서드를 호출할 때 오류가 발생합니다. 귀하의 타입이 기본형보다 더 큰 tp_basicsize 값을 갖도록 보장함으로써 이 문제를 피할 수 있습니다. 대부분의 경우, 기본 형이 object 이거나 또는 다른 데이터 멤버를 추가하여 크기가 늘어나기 때문에 자연스럽게 이 조건이 충족됩니다.
클래스 플래그를 Py_TPFLAGS_DEFAULT 로 설정합니다.
.tp_flags = Py_TPFLAGS_DEFAULT,
모든 형은 이 상수를 플래그에 포함해야 합니다. 적어도 파이썬 3.3까지 정의된 모든 멤버를 활성화합니다. 추가 멤버가 필요하면, 해당 플래그를 OR 해야 합니다.
tp_doc에 형의 독스트링을 제공합니다.
.tp_doc = PyDoc_STR("Custom objects"),
객체 생성을 가능하게 하려면 tp_new 핸들러를 제공해야 합니다. 이는 파이썬 메서드 __new__() 와 동일하지만 명시적으로 지정되어야 합니다. 이 경우 API 함수인 PyType_GenericNew() 가 제공하는 기본 구현을 사용하면 됩니다.
.tp_new = PyType_GenericNew,
custom_module_exec() 에 있는 일부 코드를 제외하고 파일의 다른 부분은 모두 익숙한 내용일 것입니다.
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
이것은 초기화 시 NULL 로 설정했던 ob_type 을 포함하여 여러 멤버를 적절한 기본값으로 채워 넣어 Custom 형을 초기화합니다.
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
이것은 해당 형을 모듈 딕셔너리에 추가합니다. 이를 통해 Custom 클래스를 호출하여 Custom 인스턴스를 생성할 수 있습니다.
>>> import custom
>>> mycustom = custom.Custom()
이제 끝났습니다! 남은 것은 빌드하는 것뿐입니다. 위의 코드를 custom.c 라는 파일에 넣고,
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "custom"
version = "1"
pyproject.toml 이라는 파일을 만들고,
from setuptools import Extension, setup
setup(ext_modules=[Extension("custom", ["custom.c"])])
를 setup.py라는 파일에 넣은 다음; 다음을
$ python -m pip install .
셸에서 실행하면 서브 디렉터리에 custom.so 파일이 생성되고 설치됩니다. 이제 파이썬을 실행하여 import custom``을 수행하고 ``Custom 객체로 실험해 볼 수 있습니다.
그렇게 어렵지 않습니다, 그렇지 않나요?
물론, 현재 Custom 형은 그리 흥미롭지 않습니다. 데이터가 없고 아무것도 하지 않습니다. 서브 클래싱조차 할 수 없습니다.
기초 예제에 데이터와 메서드 추가하기¶
기본 예제에 데이터와 메서드를 추가해 보겠습니다. 또한 이 형을 기본 클래스로 사용할 수 있게 만들겠습니다. 이러한 기능을 추가한 새로운 모듈인 custom2 를 생성하겠습니다.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof()를 위해 */
typedef struct {
PyObject_HEAD
PyObject *first; /* 이름(first name) */
PyObject *last; /* 성(last name) */
int number;
} CustomObject;
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_XSETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_XSETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* 센티널(Sentinel) */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* 센티널(Sentinel) */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom2.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom2",
.m_doc = "Example module that creates an extension type.",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom2(void)
{
return PyModuleDef_Init(&custom_module);
}
이 버전의 모듈에는 여러 가지 변경 사항이 있습니다.
Custom 형은 이제 C 구조체에 first, last, 그리고 number*라는 세 개의 데이터 속성을 가집니다. *first*와 *last 변수는 이름(성/이름)을 포함하는 파이썬 문자열이며, number 속성은 C 정수입니다.
객체 구조체는 다음과 같이 갱신됩니다:
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} CustomObject;
이제 관리할 데이터가 있기 때문에, 객체 할당과 할당 해제에 관해 더욱 신중해야 합니다. 최소한, 할당 해제 메서드가 필요합니다:
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
이는 tp_dealloc 멤버에 대입됩니다:
.tp_dealloc = Custom_dealloc,
이 메서드는 먼저 두 파이썬 속성의 참조 횟수를 제거합니다. Py_XDECREF() 는 인수가 NULL 인 경우(예를 들어 tp_new 가 도중에 실패한 경우)를 올바르게 처리합니다. 그 다음 객체의 형(Py_TYPE(self) 로 계산됨)의 tp_free 멤버를 호출하여 객체의 메모리를 해제합니다. 이때 객체의 형이 CustomType 이 아닐 수도 있는데, 이는 객체가 서브클래스의 인스턴스일 수 있기 때문입니다.
참고
위의 CustomObject * 로의 명시적 형변환이 필요한 이유는 tp_dealloc 함수 포인터가 PyObject * 인자를 받기를 기대하기 때문에 Custom_dealloc 을 PyObject * 를 인자로 취하도록 정의했기 때문입니다. 특정 형의 tp_dealloc 슬롯에 할당함으로써 해당 기능이 우리의 CustomObject 클래스 인스턴스로만 호출될 수 있음을 선언하므로, (CustomObject *) 로의 형변환은 안전합니다. 이것이 C 언어에서의 객체 지향 폴리모피즘입니다!
기존 코드나 이 튜토리얼의 이전 버전에서는 다음과 같이 유사한 함수들이 서브타입 객체 구조체에 대한 포인터(CustomObject*)를 직접 받는 것을 볼 수도 있습니다:
Custom_dealloc(CustomObject *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *) self);
}
...
.tp_dealloc = (destructor) Custom_dealloc,
이 방식은 CPython이 지원하는 모든 아키텍처에서 동일하게 동작하지만, C 표준에 따르면 정의되지 않은 동작(undefined behavior)을 유발합니다.
우리는 성과 이름이 빈 문자열로 초기화되도록 하고 싶어서, tp_new 구현을 제공합니다:
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyUnicode_FromString("");
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = PyUnicode_FromString("");
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
그리고 그것을 tp_new 멤버에 설치합니다:
.tp_new = Custom_new,
tp_new 핸들러는 타입을 위한 객체를 생성(초기화하는 것이 아님)하는 역할을 합니다. 이는 Python에서는 __new__() 메서드로 노출됩니다. tp_new 멤버를 정의할 필요는 없으며, 실제로 많은 확장 타입은 위 Custom 타입의 첫 번째 버전에서 수행된 것처럼 PyType_GenericNew() 를 재사용할 것입니다. 이 경우, 우리는 tp_new 핸들러를 사용하여 first 와 last 속성을 비-NULL 기본값으로 초기화합니다.
tp_new는 인스턴스 화 되는 형(서브 클래스가 인스턴스 화 되면, 반드시 CustomType일 필요는 없습니다)과 형이 호출될 때 전달된 모든 인자가 전달되며, 만들어진 인스턴스를 반환할 것으로 기대됩니다. tp_new 처리기는 항상 위치와 키워드 인자를 받아들이지만, 종종 인자를 무시하고 인자 처리를 초기화 (C의 tp_init나 파이썬의 __init__) 메서드에게 남겨둡니다.
참고
인터프리터가 직접 할 것이라서, tp_new는 명시적으로 tp_init를 호출하면 안 됩니다.
tp_new 구현은 tp_alloc 슬롯을 호출하여 메모리를 할당합니다:
self = (CustomObject *) type->tp_alloc(type, 0);
메모리 할당이 실패할 수 있어서, 진행하기 전에 tp_alloc 결과가 NULL이 아닌지 확인해야 합니다.
참고
우리는 tp_alloc 슬롯을 직접 채우지 않았습니다. 대신 PyType_Ready()가 베이스 클래스(기본적으로 object입니다)에서 상속하여 이를 채웁니다. 대부분의 형은 기본 할당 전략을 사용합니다.
참고
협력적인(co-operative) tp_new (기본 형의 tp_new 또는 __new__() 를 호출하는 경우)를 생성할 때, 런타임에 메서드 분해 순서(MRO)를 사용하여 어떤 메서드를 호출할지 결정해서는 안 됩니다. 항상 호출할 형을 정적으로 결정하고, 해당 형의 tp_new 를 직접 또는 type->tp_base->tp_new 를 통해 호출해야 합니다. 이렇게 하지 않으면 다른 파이썬 정의 클래스도 상속받는 여러분의 형의 파이썬 서브클래스가 제대로 작동하지 않을 수 있습니다. (구체적으로, 이러한 서브클래스의 인스턴스를 생성할 때 TypeError 가 발생할 수 있습니다.)
인스턴스의 초깃값을 제공하는 인자를 받아들이는 초기화 함수도 정의합니다:
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL, *tmp;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_XDECREF(tmp);
}
if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_XDECREF(tmp);
}
return 0;
}
이것으로 tp_init 슬롯을 채웁니다:
.tp_init = Custom_init,
tp_init 슬롯은 파이썬에서 __init__() 메서드로 노출됩니다. 이는 객체가 생성된 후 초기화하는 데 사용됩니다. 초기화 함수는 항상 위치 및 키워드 인자를 허용하며, 성공 시 0, 오류 시 -1 을 반환해야 합니다.
tp_new 핸들러와 달리, tp_init 이 호출된다는 보장이 없습니다(예를 들어, pickle 모듈은 기본적으로 언피클링된 인스턴스에 대해 __init__() 을 호출하지 않습니다). 또한 여러 번 호출될 수도 있습니다. 누구나 우리의 객체에서 __init__() 메서드를 호출할 수 있으므로, 새로운 속성 값을 할당할 때 각별히 주의해야 합니다. 예를 들어 다음과 같이 first 멤버를 할당하고 싶은 유혹을 느낄 수 있습니다:
if (first) {
Py_XDECREF(self->first);
Py_INCREF(first);
self->first = first;
}
하지만 이는 위험할 수 있습니다. 우리의 형은 first 멤버의 타입을 제한하지 않으므로, 어떤 종류의 객체든 될 수 있습니다. 이 객체의 소멸자가 first 멤버에 접근을 시도하는 코드를 실행하게 하거나, 스레드 상태 를 분리하여 다른 스레드에서 임의의 코드가 실행되어 우리의 객체에 접근하고 수정하게 만들 수도 있습니다.
편집증적이 되고 이 가능성으로부터 우리 자신을 보호하기 위해, 우리는 거의 항상 참조 횟수를 줄이기 전에 멤버를 다시 대입합니다. 언제 이렇게 하지 않아도 될까요?
참조 횟수가 1보다 크다는 것을 확실히 알고 있을 때;
when we know that deallocation of the object [1] will neither detach the thread state nor cause any calls back into our type’s code;
순환 가비지 수거를 지원하지 않는 형의
tp_dealloc처리기에서 참조 횟수를 감소시킬 때 [2].
인스턴스 변수를 어트리뷰트로 노출하려고 합니다. 이를 수행하는 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 멤버 정의를 정의하는 것입니다:
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
그리고 tp_members 슬롯에 정의를 넣습니다:
.tp_members = Custom_members,
각 멤버 정의에는 멤버 이름, 형, 오프셋, 액세스 플래그 및 독스트링이 있습니다. 자세한 내용은 아래 범용 어트리뷰트 관리 섹션을 참조하십시오.
이 접근법의 단점은 파이썬 어트리뷰트에 대입할 수 있는 객체의 형을 제한할 방법을 제공하지 않는다는 것입니다. 이름과 성은 문자열일 것으로 기대하지만, 모든 파이썬 객체를 할당할 수 있습니다. 또한 어트리뷰트를 삭제할 수 있습니다, C 포인터를 NULL로 설정합니다. NULL이 아닌 값으로 멤버를 초기화 할 수 있지만, 어트리뷰트를 삭제하면 멤버를 NULL로 설정할 수 있습니다.
우리는 첫 번째 이름과 마지막 이름을 결합하여 객체의 이름을 출력하는 단일 메서드인 Custom.name() 을 정의합니다.
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
이 메서드는 첫 번째 인자로 Custom (또는 Custom 서브클래스) 인스턴스를 받는 C 함수로 구현됩니다. 메서드는 항상 첫 번째 인자로 인스턴스를 받습니다. 메서드는 종종 위치 및 키워드 인자도 취하지만, 이 경우 우리는 어떤 인자도 받지 않으며 위치 인자 튜플이나 키워드 인자 딕셔너리를 수락할 필요도 없습니다. 이 메서드는 다음 파이썬 메서드와 동일합니다:
def name(self):
return "%s %s" % (self.first, self.last)
우리의 first 와 last 멤버가 NULL 일 가능성을 확인해야 한다는 점에 유의하십시오. 이들은 삭제될 수 있으며, 그 경우 NULL 로 설정됩니다. 이러한 속성의 삭제를 방지하고 속성 값을 문자열로 제한하는 것이 더 좋습니다. 다음 섹션에서 그 방법을 알아보겠습니다.
이제 메서드를 정의했습니다, 메서드 정의 배열을 만들어야 합니다:
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"첫 번째 이름과 마지막 이름을 결합하여 이름을 반환함"
},
{NULL} /* Sentinel */
};
(메서드가 self 외에 다른 인자를 기대하지 않음을 나타내기 위해 METH_NOARGS 플래그를 사용했습니다.)
그리고 tp_methods 슬롯에 대입합니다:
.tp_methods = Custom_methods,
마지막으로, 우리의 형을 서브클래싱을 위한 베이스 클래스로 사용할 수 있게 만들 것입니다. 우리는 메서드를 작성할 때 생성되거나 사용되는 객체의 타입에 대해 아무런 가정을 하지 않도록 주의를 기울였으므로, 클래스 플래그 정의에 Py_TPFLAGS_BASETYPE 만 추가하면 됩니다:
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
우리는 PyInit_custom`을 :c:func:()!PyInit_custom2`로 이름을 바꾸고, PyModuleDef 구조체에서 모듈 이름을 업데이트하며, PyTypeObject 구조체에서 전체 클래스 이름을 업데이트합니다.
마지막으로, 새 모듈을 포함하도록 setup.py 파일을 업데이트합니다.
from setuptools import Extension, setup
setup(ext_modules=[
Extension("custom", ["custom.c"]),
Extension("custom2", ["custom2.c"]),
])
그런 다음, import custom2 를 할 수 있도록 재설치합니다.
$ python -m pip install .
데이터 어트리뷰트를 더 세밀하게 제어하기¶
이 섹션에서는 Custom 예제에서 first 및 last 속성이 설정되는 방식에 대해 더 세밀한 제어를 제공할 것입니다. 이전 버전의 모듈에서 인스턴스 변수 first`와 :attr:!last`는 비문자열 값으로 설정되거나 심지어 삭제될 수도 있었습니다. 우리는 이 속성들이 항상 문자열을 포함하도록 보장하고 싶습니다.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof()를 위해 */
typedef struct {
PyObject_HEAD
PyObject *first; /* 이름(first name) */
PyObject *last; /* 성(last name) */
int number;
} CustomObject;
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* 센티널(Sentinel) */
};
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->first);
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
Py_SETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->last);
}
static int
Custom_setlast(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last attribute value must be a string");
return -1;
}
Py_SETREF(self->last, Py_NewRef(value));
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"first name", NULL},
{"last", Custom_getlast, Custom_setlast,
"last name", NULL},
{NULL} /* 센티널(Sentinel) */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* 센티널(Sentinel) */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom3.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom3",
.m_doc = "Example module that creates an extension type.",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom3(void)
{
return PyModuleDef_Init(&custom_module);
}
first 및 last 속성에 대해 더 나은 제어를 제공하기 위해 사용자 정의 게터(getter)와 세터(setter) 함수를 사용할 것입니다. 다음은 first 속성을 가져오고 설정하는 함수입니다:
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
Py_INCREF(self->first);
return self->first;
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
PyObject *tmp;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
tmp = self->first;
Py_INCREF(value);
self->first = value;
Py_DECREF(tmp);
return 0;
}
게터 함수는 Custom 객체와 void 포인터인 “클로저”를 인자로 받습니다. 이 경우 클로저는 무시됩니다. (클로저는 게터와 세터에 정의 데이터를 전달하는 고급 기능을 지원합니다. 예를 들어, 클로저의 데이터에 기반하여 가져오거나 설정할 속성을 결정하는 단일 세트의 게터 및 세터 함수를 허용하기 위해 사용될 수 있습니다.)
세터 함수는 Custom 객체, 새 값, 그리고 클로저를 인자로 받습니다. 새 값이 NULL 인 경우 속성이 삭제되는 것입니다. 우리의 세터에서는 속성이 삭제되거나 새로운 값이 문자열이 아닌 경우 오류를 발생시킵니다.
PyGetSetDef 구조체의 배열을 만듭니다:
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"first name", NULL},
{"last", Custom_getlast, Custom_setlast,
"last name", NULL},
{NULL} /* Sentinel */
};
그리고 tp_getset 슬롯에 등록합니다:
.tp_getset = Custom_getsetters,
PyGetSetDef 구조체의 마지막 항목은 위에서 언급한 “클로저”입니다. 이 경우, 클로저를 사용하지 않아서, NULL만 전달합니다.
또한 이러한 어트리뷰트에 대한 멤버 정의를 제거합니다:
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
또한 문자열만 전달되도록 [3] tp_init 처리기를 갱신해야 합니다:
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL, *tmp;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_DECREF(tmp);
}
if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_DECREF(tmp);
}
return 0;
}
이러한 변경을 통해, first와 last 멤버가 절대 NULL이 아니라고 확신할 수 있어서, 거의 모든 경우에 NULL 값 검사를 제거할 수 있습니다. 이것은 대부분의 Py_XDECREF() 호출이 Py_DECREF() 호출로 변환될 수 있음을 의미합니다. 이러한 호출을 변경할 수 없는 유일한 장소는 tp_dealloc 구현에서인데, tp_new에서 이 멤버의 초기화가 실패했을 가능성이 있습니다.
또한 이전과 마찬가지로, 초기화 함수에서 모듈 초기화 함수와 모듈 이름을 바꾸고, setup.py 파일에 추가 정의를 추가합니다.
순환 가비지 수거 지원하기¶
파이썬에는 참조 횟수가 0이 아닐 때도 불필요한 객체를 식별할 수 있는 순환 가비지 수거기 (GC)가 있습니다. 이것은 객체가 순환에 참여할 때 일어날 수 있습니다. 예를 들어, 다음을 고려하십시오:
>>> l = []
>>> l.append(l)
>>> del l
이 예에서, 자신을 포함하는 리스트를 만듭니다. 삭제해도 여전히 자체 참조가 있습니다. 참조 횟수가 0으로 떨어지지 않습니다. 다행스럽게도, 파이썬의 순환 가비지 수거기는 결국 리스트가 가비지임을 확인하고 해제합니다.
Custom 예제의 두 번째 버전에서는 임의의 객체를 first 또는 last 속성에 저장할 수 있도록 허용했습니다 [4]. 게다가, 두 번째 및 세 번째 버전에서는 Custom`의 하위 클래스화를 허용했으며, 하위 클래스는 임의의 속성을 추가할 수 있습니다. 위 두 가지 이유로 인해, :class:!Custom` 객체는 순환에 참여할 수 있습니다:
>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n
Custom 인스턴스가 참조 순환에 참여할 때 순환 GC(cyclic GC)에 의해 적절히 탐지되고 수거되도록 하려면, 우리의 Custom 형은 두 개의 추가 슬롯을 채우고 이 슬롯들을 활성화하는 플래그를 설정해야 합니다:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof()를 위함 */
typedef struct {
PyObject_HEAD
PyObject *first; /* 이름 (first name) */
PyObject *last; /* 성 (last name) */
int number;
} CustomObject;
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
static int
Custom_clear(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
static void
Custom_dealloc(PyObject *op)
{
PyObject_GC_UnTrack(op);
(void)Custom_clear(op);
Py_TYPE(op)->tp_free(op);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* 센티넬 (Sentinel) */
};
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->first);
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
Py_XSETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->last);
}
static int
Custom_setlast(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last attribute value must be a string");
return -1;
}
Py_XSETREF(self->last, Py_NewRef(value));
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"first name", NULL},
{"last", Custom_getlast, Custom_setlast,
"last name", NULL},
{NULL} /* 센티넬 (Sentinel) */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* 센티넬 (Sentinel) */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom4.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_traverse = Custom_traverse,
.tp_clear = Custom_clear,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom4",
.m_doc = "Example module that creates an extension type.",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom4(void)
{
return PyModuleDef_Init(&custom_module);
}
첫째, 탐색(traversal) 메서드는 순환 GC가 순환에 참여할 수 있는 서브 객체에 대해 알 수 있도록 합니다:
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
int vret;
if (self->first) {
vret = visit(self->first, arg);
if (vret != 0)
return vret;
}
if (self->last) {
vret = visit(self->last, arg);
if (vret != 0)
return vret;
}
return 0;
}
순환에 참여할 수 있는 각 서브 객체에 대해, 탐색 메서드에 전달되는 visit() 함수를 호출해야 합니다. visit() 함수는 인자로 서브 객체와 탐색 메서드에 전달된 추가 인자 arg 을 받습니다. 이 함수는 0이 아닌 값이면 그대로 반환되어야 하는 정수 값을 반환합니다.
파이썬은 visit 함수 호출을 자동화하는 Py_VISIT() 매크로를 제공합니다. Py_VISIT()를 사용하면, Custom_traverse에서 관용구 양을 최소화할 수 있습니다:
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
참고
Py_VISIT()를 사용하려면 tp_traverse 구현에서 인자 이름을 visit과 arg로 정확하게 지정해야 합니다.
둘째, 순환에 참여할 수 있는 서브 객체를 지우는 메서드를 제공해야 합니다:
static int
Custom_clear(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
Py_CLEAR() 매크로 사용에 주목하십시오. 참조 횟수를 줄이면서 임의 형의 데이터 어트리뷰트를 지우는 권장되고 안전한 방법입니다. NULL로 설정하기 전에 어트리뷰트에서 Py_XDECREF()를 대신 호출했으면, 어트리뷰트의 파괴자가 어트리뷰트를 다시 읽는 코드(특히 참조 순환이 있으면 )를 다시 호출할 가능성이 있습니다.
참고
다음과 같이 작성하여 Py_CLEAR()를 에뮬레이션할 수 있습니다:
PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);
그런데도, 어트리뷰트를 삭제할 때 항상 Py_CLEAR()를 사용하기가 훨씬 쉽고 에러가 적습니다. 견고성을 희생하면서 세밀한 최적화를 시도하지 마십시오!
할당 해제기 Custom_dealloc은 어트리뷰트를 지울 때 임의의 코드를 호출할 수 있습니다. 이는 함수 내에서 순환 GC가 트리거 될 수 있음을 의미합니다. GC는 참조 횟수가 0이 아니라고 가정하기 때문에, 멤버를 지우기 전에 PyObject_GC_UnTrack()을 호출하여 GC에서 객체를 추적 해제해야 합니다. 다음은 PyObject_GC_UnTrack()과 Custom_clear를 사용하여 다시 구현된 할당 해제기입니다:
static void
Custom_dealloc(PyObject *op)
{
PyObject_GC_UnTrack(op);
(void)Custom_clear(op);
Py_TYPE(op)->tp_free(op);
}
마지막으로, 클래스 플래그에 Py_TPFLAGS_HAVE_GC 를 추가합니다:
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
거의 다 됐습니다. 사용자 정의 tp_alloc이나 tp_free 처리기를 작성했으면, 순환 가비지 수거를 위해 이를 수정해야 합니다. 대부분의 확장은 자동으로 제공된 버전을 사용합니다.
다른 형의 서브 클래싱¶
기존 형에서 파생된 새 확장형을 만들 수 있습니다. 확장이 필요한 PyTypeObject를 쉽게 사용할 수 있어서, 내장형에서 상속하기가 가장 쉽습니다. 확장 모듈 간에 이러한 PyTypeObject 구조체를 공유하기 어려울 수 있습니다.
이 예제에서는 내장된 list 형을 상속받는 SubList 형을 생성합니다. 새로운 형은 일반 리스트와 완전히 호환되지만, 내부 카운터를 증가시키는 추가적인 increment() 메서드를 가집니다:
>>> import sublist
>>> s = sublist.SubList(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyListObject list;
int state;
} SubListObject;
static PyObject *
SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy))
{
SubListObject *self = (SubListObject *) op;
self->state++;
return PyLong_FromLong(self->state);
}
static PyMethodDef SubList_methods[] = {
{"increment", SubList_increment, METH_NOARGS,
PyDoc_STR("increment state counter")},
{NULL},
};
static int
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
SubListObject *self = (SubListObject *) op;
if (PyList_Type.tp_init(op, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
static PyTypeObject SubListType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "sublist.SubList",
.tp_doc = PyDoc_STR("SubList objects"),
.tp_basicsize = sizeof(SubListObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_init = SubList_init,
.tp_methods = SubList_methods,
};
static int
sublist_module_exec(PyObject *m)
{
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot sublist_module_slots[] = {
{Py_mod_exec, sublist_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef sublist_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "sublist",
.m_doc = "Example module that creates an extension type.",
.m_size = 0,
.m_slots = sublist_module_slots,
};
PyMODINIT_FUNC
PyInit_sublist(void)
{
return PyModuleDef_Init(&sublist_module);
}
보시다시피 소스 코드는 이전 섹션의 Custom 예제와 매우 유사합니다. 주요 차이점을 분석해 보겠습니다.
typedef struct {
PyListObject list;
int state;
} SubListObject;
파생형 객체의 주요 차이점은 베이스형의 객체 구조체가 첫 번째 값이어야 한다는 것입니다. 베이스형은 이미 구조체의 시작 부분에 PyObject_HEAD()를 포함합니다.
파이썬 객체가 SubList 인스턴스인 경우, 해당 객체의 PyObject * 포인터를 PyListObject *``와 ``SubListObject * 모두로 안전하게 형변환할 수 있습니다:
static int
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
SubListObject *self = (SubListObject *) op;
if (PyList_Type.tp_init(op, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
위에서 베이스 형의 __init__() 메서드로 연결하여 호출하는 방법을 확인했습니다.
이 패턴은 사용자 정의 tp_new와 tp_dealloc 멤버를 갖는 형을 작성할 때 중요합니다. tp_new 처리기는 실제로 tp_alloc을 사용하여 객체의 메모리를 만들지 말고, 베이스 클래스가 자체 tp_new를 호출하여 처리하도록 해야 합니다.
PyTypeObject 구조체는 형의 구체적인 베이스 클래스를 지정하는 tp_base 를 지원합니다. 크로스 플랫폼 컴파일러 문제로 인해, 해당 필드를 PyList_Type 에 대한 참조로 직접 채울 수 없습니다. 이는 Py_mod_exec 함수에서 수행되어야 합니다:
static int
sublist_module_exec(PyObject *m)
{
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
return -1;
}
return 0;
}
PyType_Ready()를 호출하기 전에, 형 구조체에 tp_base 슬롯이 채워져 있어야 합니다. 기존 형을 파생할 때, PyType_GenericNew()로 tp_alloc 슬롯을 채울 필요는 없습니다 – 베이스형의 할당 함수가 상속됩니다.
그 후에, PyType_Ready() 를 호출하고 형 객체를 모듈에 추가하는 과정은 기본적인 Custom 예제와 동일합니다.
각주