2. 확장형 정의하기: 자습서¶
파이썬은 C 확장 모듈 작성자가 내장 str
과 list
형과 마찬가지로 파이썬 코드에서 조작할 수 있는 새로운 형을 정의할 수 있도록 합니다. 모든 확장형의 코드는 패턴을 따르지만, 시작하기 전에 이해해야 할 세부 사항이 있습니다. 이 설명서는 주제에 대한 간단한 소개입니다.
2.1. 기초¶
CPython 런타임은 모든 파이썬 객체를 PyObject*
형의 변수로 간주하는데, 이는 모든 파이썬 객체의 “베이스형” 역할을 합니다. PyObject
구조체 자체는 객체의 참조 횟수와 객체의 “형 객체”에 대한 포인터만 포함합니다. 여기가 액션이 일어나는 곳입니다; 형 객체는 예를 들어 객체에서 어트리뷰트를 조회하거나, 메서드를 호출하거나, 다른 객체와 곱할 때 인터프리터가 호출하는 (C) 함수를 결정합니다. 이러한 C 함수를 “형 메서드”라고 합니다.
따라서, 새 확장형을 정의하려면, 새 형 객체를 만들어야 합니다.
이런 종류의 것은 예제로만 설명할 수 있어서, 여기에 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 = {
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 PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_custom(void)
{
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
return m;
}
이제는 한 번에 배워야 할 것이 많지만, 이전 장과 비슷해 보이기를 바랍니다. 이 파일은 세 가지를 정의합니다:
Custom
객체에 포함된 것:CustomObject
구조체이며, 각Custom
인스턴스마다 한 번씩 할당됩니다.Custom
형의 작동 방식:CustomType
구조체이며, 특정 연산이 요청될 때 인터프리터가 검사하는 플래그와 함수 포인터 집합을 정의합니다.custom
모듈을 초기화하는 방법:PyInit_custom
함수와 관련custommodule
구조체입니다.
첫 번째 것은:
typedef struct {
PyObject_HEAD
} CustomObject;
This is what a Custom object will contain. PyObject_HEAD
is mandatory
at the start of each object struct and defines a field called ob_base
of type PyObject
, containing a pointer to a type object and a
reference count (these can be accessed using the macros Py_TYPE
and Py_REFCNT
respectively). The reason for the macro is to
abstract away the layout and to enable additional fields in debug builds.
참고
PyObject_HEAD
매크로 뒤에는 세미콜론이 없습니다. 실수로 추가하는 것에 주의하십시오: 일부 컴파일러는 불평할 것입니다.
물론, 객체는 일반적으로 표준 PyObject_HEAD
관용구 외에 추가 데이터를 저장합니다; 예를 들어, 표준 파이썬 floats에 대한 정의는 다음과 같습니다:
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
두 번째 것은 형 객체의 정의입니다.
static PyTypeObject CustomType = {
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으로 채워지며, 필요하지 않으면 명시적으로 지정하지 않는 것이 일반적입니다.
한 번에 한 필드씩 따로 다루려고 합니다:
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
이름이 모듈 이름과 모듈 내 형의 이름을 모두 포함하는 점으로 구분된 이름임에 유의하십시오. 이 경우 모듈은 custom
이고 형은 Custom
이라서, 형 이름을 custom.Custom
으로 설정합니다. 형이 pydoc
과 pickle
모듈과 호환되도록 하려면 실제 점으로 구분된 임포트 경로를 사용하는 것이 중요합니다.
.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,
PyInit_custom()
의 일부 코드를 제외하고, 파일의 다른 모든 내용은 익숙해야 합니다:
if (PyType_Ready(&CustomType) < 0)
return;
이것은 Custom
형을 초기화하는데, 처음에 NULL
로 설정한 ob_type
을 포함하여, 여러 멤버를 적절한 기본값으로 채웁니다.
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
이것은 형을 모듈 딕셔너리에 추가합니다. Custom
클래스를 호출하여 Custom
인스턴스를 만들 수 있도록 합니다:
>>> import custom
>>> mycustom = custom.Custom()
이게 전부입니다! 남아있는 것은 빌드하는 것입니다; 위의 코드를 custom.c
라는 파일에 넣고:
from distutils.core import setup, Extension
setup(name="custom", version="1.0",
ext_modules=[Extension("custom", ["custom.c"])])
를 setup.py
라는 파일에 넣은 다음; 다음을
$ python setup.py build
셸에서 입력하면 서브 디렉터리에 파일 custom.so
를 생성해야 합니다; 해당 디렉터리로 이동하여 파이썬을 시작하십시오 — import custom
할 수 있고 Custom 객체로 놀 수 있습니다.
그렇게 어렵지 않습니다, 그렇지 않나요?
물론, 현재 Custom 형은 그리 흥미롭지 않습니다. 데이터가 없고 아무것도 하지 않습니다. 서브 클래싱조차 할 수 없습니다.
참고
이 설명서는 C 확장을 빌드하기 위한 표준 distutils
모듈을 보여 주지만, 실제 사용 사례에서는 새롭고 유지 관리가 잘 된 setuptools
라이브러리를 사용하는 것이 좋습니다. 이 작업을 수행하는 방법에 대한 설명서는 이 문서의 범위를 넘어서고 파이썬 패키징 사용자 지침서에서 찾을 수 있습니다.
2.2. 기초 예제에 데이터와 메서드 추가하기¶
데이터와 메서드를 추가하도록 기초 예제를 확장해 봅시다. 형을 베이스 클래스로도 사용할 수 있도록 합시다. 다음 기능을 추가하는 새 모듈 custom2
를 만들 것입니다:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} CustomObject;
static void
Custom_dealloc(CustomObject *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
}
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
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;
}
static PyMemberDef Custom_members[] = {
{"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
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", (PyCFunction) Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject CustomType = {
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 = (initproc) Custom_init,
.tp_dealloc = (destructor) Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
};
static PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom2",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_custom2(void)
{
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
return m;
}
이 버전의 모듈에는 여러 가지 변경 사항이 있습니다.
다음과 같은 추가 포함(include)을 추가했습니다:
#include <structmember.h>
이 포함은 나중에 설명하는 것처럼 어트리뷰트를 처리하는 데 사용하는 선언을 제공합니다.
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(CustomObject *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *) self);
}
이는 tp_dealloc
멤버에 대입됩니다:
.tp_dealloc = (destructor) Custom_dealloc,
이 메서드는 먼저 두 파이썬 어트리뷰트의 참조 횟수를 지웁니다. Py_XDECREF()
는 인자가 NULL
인 경우(tp_new
가 중간에 실패하면 발생할 수 있습니다)를 올바르게 처리합니다. 그런 다음 객체 형(Py_TYPE(self)
로 계산합니다)의 tp_free
멤버를 호출하여 객체의 메모리를 해제합니다. 객체 형이 CustomType
이 아닐 수 있음에 유의하십시오, 객체는 서브 클래스의 인스턴스일 수 있기 때문입니다.
참고
CustomObject *
인자를 취하도록 Custom_dealloc
을 정의했지만, tp_dealloc
함수 포인터는 PyObject *
인자를 받을 것으로 기대하기 때문에 위의 destructor
로의 명시적 캐스트가 필요합니다. 그렇지 않으면, 컴파일러에서 경고가 발생합니다. 이것이 C로 하는 객체 지향 다형성입니다!
우리는 성과 이름이 빈 문자열로 초기화되도록 하고 싶어서, 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
처리기는 형의 객체를 (초기화와 대비하여) 생성하는 책임을 집니다. 파이썬에서 __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
입니다)에서 상속하여 이를 채웁니다. 대부분의 형은 기본 할당 전략을 사용합니다.
참고
협업 tp_new
(베이스형의 tp_new
나 __new__()
를 호출하는 것)를 만드는 경우, 실행 시간에 메서드 결정 순서를 사용하여 호출할 메서드를 결정하려고 하지 않아야 합니다. 항상 어떤 형을 호출할지 정적으로 결정하고, 그것의 tp_new
를 직접, 또는 type->tp_base->tp_new
를 통해 호출하십시오. 이렇게 하지 않으면, 다른 파이썬 정의 클래스도 상속하는 여러분 형의 파이썬 서브 클래스가 올바르게 작동하지 않을 수 있습니다. (특히, TypeError
를 얻지 않으면서, 이러한 서브 클래스의 인스턴스를 만들지 못할 수도 있습니다.)
인스턴스의 초깃값을 제공하는 인자를 받아들이는 초기화 함수도 정의합니다:
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
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 = (initproc) 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보다 크다는 것을 확실히 알고 있을 때;
순환 가비지 수거를 지원하지 않는 형의
tp_dealloc
처리기에서 참조 횟수를 감소시킬 때 2.
인스턴스 변수를 어트리뷰트로 노출하려고 합니다. 이를 수행하는 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 멤버 정의를 정의하는 것입니다:
static PyMemberDef Custom_members[] = {
{"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
"last name"},
{"number", 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(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
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", (PyCFunction) Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
(우리는 METH_NOARGS
플래그를 사용하여 메서드가 self 이외의 인자를 기대하지 않음을 나타냈음에 유의하십시오)
그리고 tp_methods
슬롯에 대입합니다:
.tp_methods = Custom_methods,
마지막으로, 우리의 형을 서브 클래싱의 베이스 클래스로 사용할 수 있게 만들 것입니다. 우리는 지금까지 만들어지거나 사용되고 있는 객체의 형에 대해 가정을 하지 않도록 메서드를 주의해서 작성했습니다, 그래서 클래스 플래그 정의에 Py_TPFLAGS_BASETYPE
을 추가하기만 하면 됩니다.
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
PyInit_custom()
의 이름을 PyInit_custom2()
로 바꾸고, PyModuleDef
구조체에서 모듈 이름을 갱신하고, PyTypeObject
구조체에서 전체 클래스 이름을 갱신합니다.
마지막으로, 새 모듈을 빌드하기 위해 setup.py
파일을 갱신합니다:
from distutils.core import setup, Extension
setup(name="custom", version="1.0",
ext_modules=[
Extension("custom", ["custom.c"]),
Extension("custom2", ["custom2.c"]),
])
2.3. 데이터 어트리뷰트를 더 세밀하게 제어하기¶
이 섹션에서는, Custom
예제에서 first
와 last
어트리뷰트가 설정되는 방식을 더 세밀하게 제어합니다. 이전 버전의 모듈에서는, 인스턴스 변수 first
와 last
를 문자열이 아닌 값으로 설정하거나 삭제할 수도 있습니다. 이 어트리뷰트들에 항상 문자열이 포함되도록 하고 싶습니다.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} CustomObject;
static void
Custom_dealloc(CustomObject *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
}
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
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;
}
static PyMemberDef Custom_members[] = {
{"number", T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
Py_INCREF(self->first);
return self->first;
}
static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
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;
}
static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
Py_INCREF(self->last);
return self->last;
}
static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
PyObject *tmp;
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;
}
tmp = self->last;
Py_INCREF(value);
self->last = value;
Py_DECREF(tmp);
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
"first name", NULL},
{"last", (getter) Custom_getlast, (setter) Custom_setlast,
"last name", NULL},
{NULL} /* Sentinel */
};
static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", (PyCFunction) Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject CustomType = {
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 = (initproc) Custom_init,
.tp_dealloc = (destructor) Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom3",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_custom3(void)
{
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
return m;
}
first
와 last
어트리뷰트를 더 효과적으로 제어하기 위해, 사용자 정의 게터(getter)와 세터(setter) 함수를 사용합니다. first
어트리뷰트를 가져오고 설정하는 함수는 다음과 같습니다:
static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
Py_INCREF(self->first);
return self->first;
}
static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
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;
}
게터(getter) 함수에는 Custom
객체와 “클로저(closure)”(void 포인터)가 전달됩니다. 이 경우, 클로저는 무시됩니다. (클로저는 정의 데이터가 게터(getter)와 세터(setter)로 전달되는 고급 사용법을 지원합니다. 예를 들어, 클로저의 데이터에 기반하여 가져오거나 설정할 어트리뷰트를 결정하는 단일 게터(getter)와 세터(setter) 함수 집합을 가능하게 합니다.)
세터(setter) 함수에는 Custom
객체, 새 값 및 클로저(closure)가 전달됩니다. 새 값은 NULL
일 수 있으며, 이 경우 어트리뷰트가 삭제됩니다. 세터(setter)에서, 우리는 어트리뷰트가 삭제되거나 새 값이 문자열이 아니면 에러를 발생시킵니다.
PyGetSetDef
구조체의 배열을 만듭니다:
static PyGetSetDef Custom_getsetters[] = {
{"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
"first name", NULL},
{"last", (getter) Custom_getlast, (setter) Custom_setlast,
"last name", NULL},
{NULL} /* Sentinel */
};
그리고 tp_getset
슬롯에 등록합니다:
.tp_getset = Custom_getsetters,
PyGetSetDef
구조체의 마지막 항목은 위에서 언급한 “클로저”입니다. 이 경우, 클로저를 사용하지 않아서, NULL
만 전달합니다.
또한 이러한 어트리뷰트에 대한 멤버 정의를 제거합니다:
static PyMemberDef Custom_members[] = {
{"number", T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
또한 문자열만 전달되도록 3 tp_init
처리기를 갱신해야 합니다:
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
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
파일에 추가 정의를 추가합니다.
2.4. 순환 가비지 수거 지원하기¶
파이썬에는 참조 횟수가 0이 아닐 때도 불필요한 객체를 식별할 수 있는 순환 가비지 수거기 (GC)가 있습니다. 이것은 객체가 순환에 참여할 때 일어날 수 있습니다. 예를 들어, 다음을 고려하십시오:
>>> l = []
>>> l.append(l)
>>> del l
이 예에서, 자신을 포함하는 리스트를 만듭니다. 삭제해도 여전히 자체 참조가 있습니다. 참조 횟수가 0으로 떨어지지 않습니다. 다행스럽게도, 파이썬의 순환 가비지 수거기는 결국 리스트가 가비지임을 확인하고 해제합니다.
Custom
예제의 두 번째 버전에서는, 모든 종류의 객체를 first
나 last
어트리뷰트에 저장할 수 있었습니다 4. 게다가 두 번째와 세 번째 버전에서는, Custom
을 서브 클래싱 할 수 있었고, 서브 클래스는 임의의 어트리뷰트를 추가할 수 있습니다. 이 두 가지 이유 중 어느 것으로도, Custom
객체는 순환에 참여할 수 있습니다:
>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n
순환 GC가 참조 순환에 참여하는 Custom
인스턴스를 올바르게 감지하고 수집할 수 있도록, Custom
형은 두 개의 추가 슬롯을 채우고 이러한 슬롯을 활성화하는 플래그를 활성화해야 합니다:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} CustomObject;
static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
static int
Custom_clear(CustomObject *self)
{
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
static void
Custom_dealloc(CustomObject *self)
{
PyObject_GC_UnTrack(self);
Custom_clear(self);
Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
}
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
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;
}
static PyMemberDef Custom_members[] = {
{"number", T_INT, offsetof(CustomObject, number), 0,
"custom number"},
{NULL} /* Sentinel */
};
static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
Py_INCREF(self->first);
return self->first;
}
static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
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_INCREF(value);
Py_CLEAR(self->first);
self->first = value;
return 0;
}
static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
Py_INCREF(self->last);
return self->last;
}
static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
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_INCREF(value);
Py_CLEAR(self->last);
self->last = value;
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
"first name", NULL},
{"last", (getter) Custom_getlast, (setter) Custom_setlast,
"last name", NULL},
{NULL} /* Sentinel */
};
static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", (PyCFunction) Custom_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject CustomType = {
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 = (initproc) Custom_init,
.tp_dealloc = (destructor) Custom_dealloc,
.tp_traverse = (traverseproc) Custom_traverse,
.tp_clear = (inquiry) Custom_clear,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom4",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_custom4(void)
{
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
return m;
}
첫째, 탐색(traversal) 메서드는 순환 GC가 순환에 참여할 수 있는 서브 객체에 대해 알 수 있도록 합니다:
static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
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(CustomObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
참고
Py_VISIT()
를 사용하려면 tp_traverse
구현에서 인자 이름을 visit과 arg로 정확하게 지정해야 합니다.
둘째, 순환에 참여할 수 있는 서브 객체를 지우는 메서드를 제공해야 합니다:
static int
Custom_clear(CustomObject *self)
{
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(CustomObject *self)
{
PyObject_GC_UnTrack(self);
Custom_clear(self);
Py_TYPE(self)->tp_free((PyObject *) self);
}
마지막으로, Py_TPFLAGS_HAVE_GC
플래그를 클래스 플래그에 추가합니다:
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
거의 다 됐습니다. 사용자 정의 tp_alloc
이나 tp_free
처리기를 작성했으면, 순환 가비지 수거를 위해 이를 수정해야 합니다. 대부분의 확장은 자동으로 제공된 버전을 사용합니다.
2.5. 다른 형의 서브 클래싱¶
기존 형에서 파생된 새 확장형을 만들 수 있습니다. 확장이 필요한 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(SubListObject *self, PyObject *unused)
{
self->state++;
return PyLong_FromLong(self->state);
}
static PyMethodDef SubList_methods[] = {
{"increment", (PyCFunction) SubList_increment, METH_NOARGS,
PyDoc_STR("increment state counter")},
{NULL},
};
static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
static PyTypeObject SubListType = {
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 = (initproc) SubList_init,
.tp_methods = SubList_methods,
};
static PyModuleDef sublistmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "sublist",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_sublist(void)
{
PyObject *m;
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0)
return NULL;
m = PyModule_Create(&sublistmodule);
if (m == NULL)
return NULL;
Py_INCREF(&SubListType);
if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
Py_DECREF(&SubListType);
Py_DECREF(m);
return NULL;
}
return m;
}
보시다시피, 소스 코드는 이전 섹션의 Custom
예제와 매우 유사합니다. 우리는 그들 사이의 주요 차이점을 분석할 것입니다.
typedef struct {
PyListObject list;
int state;
} SubListObject;
파생형 객체의 주요 차이점은 베이스형의 객체 구조체가 첫 번째 값이어야 한다는 것입니다. 베이스형은 이미 구조체의 시작 부분에 PyObject_HEAD()
를 포함합니다.
파이썬 객체가 SubList
인스턴스일 때, PyObject *
포인터는 PyListObject *
와 SubListObject *
모두로 안전하게 캐스팅될 수 있습니다:
static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *) self, 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
에 대한 참조로 해당 필드를 직접 채울 수 없습니다; 나중에 모듈 초기화 함수에서 수행해야 합니다:
PyMODINIT_FUNC
PyInit_sublist(void)
{
PyObject* m;
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0)
return NULL;
m = PyModule_Create(&sublistmodule);
if (m == NULL)
return NULL;
Py_INCREF(&SubListType);
if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
Py_DECREF(&SubListType);
Py_DECREF(m);
return NULL;
}
return m;
}
PyType_Ready()
를 호출하기 전에, 형 구조체에 tp_base
슬롯이 채워져 있어야 합니다. 기존 형을 파생할 때, PyType_GenericNew()
로 tp_alloc
슬롯을 채울 필요는 없습니다 – 베이스형의 할당 함수가 상속됩니다.
그런 다음, PyType_Ready()
를 호출하고 형 객체를 모듈에 추가하는 것은 기초 Custom
예와 같습니다.
각주
- 1
이것은 객체가 문자열이나 부동 소수점과 같은 기본형이라는 것을 알고 있을 때 참입니다.
- 2
우리의 형이 가비지 수거를 지원하지 않기 때문에, 이 예제에서는
tp_dealloc
처리기에서 이것을 사용했습니다.- 3
우리는 이제 first와 last 멤버가 문자열이라는 것을 알고 있어서, 참조 횟수를 줄이는 데 덜 주의할 수 있지만, 우리는 문자열 서브 클래스의 인스턴스를 받아들입니다. 일반 문자열을 할당 해제하는 것이 우리 객체로 다시 호출되지는 않더라도, 문자열 서브 클래스의 인스턴스를 할당 해제하는 것이 객체로 다시 호출되지 않는다고 보장할 수 없습니다.
- 4
또한, 어트리뷰트가 문자열 인스턴스로 제한된 경우에도, 사용자는 임의의
str
서브 클래스를 전달할 수 있어서 여전히 참조 순환을 만들 수 있습니다.