2. 확장형 정의하기: 자습서

파이썬은 C 확장 모듈 작성자가 내장 strlist 형과 마찬가지로 파이썬 코드에서 조작할 수 있는 새로운 형을 정의할 수 있도록 합니다. 모든 확장형의 코드는 패턴을 따르지만, 시작하기 전에 이해해야 할 세부 사항이 있습니다. 이 설명서는 주제에 대한 간단한 소개입니다.

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 = "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;
}

이제는 한 번에 배워야 할 것이 많지만, 이전 장과 비슷해 보이기를 바랍니다. 이 파일은 세 가지를 정의합니다:

  1. Custom 객체에 포함된 것: CustomObject 구조체이며, 각 Custom 인스턴스마다 한 번씩 할당됩니다.

  2. Custom 의 작동 방식: CustomType 구조체이며, 특정 연산이 요청될 때 인터프리터가 검사하는 플래그와 함수 포인터 집합을 정의합니다.

  3. custom 모듈을 초기화하는 방법: PyInit_custom 함수와 관련 custommodule 구조체입니다.

첫 번째 것은:

typedef struct {
    PyObject_HEAD
} CustomObject;

이것이 Custom 객체에 포함될 것입니다. PyObject_HEAD는 각 객체 구조체의 시작 부분에 필수적으로 오는 것이며, PyObject 형의 ob_base라는 필드를 정의하여, 형 객체에 대한 포인터와 참조 횟수를 포함합니다 (이것들은 각각 매크로 Py_TYPEPy_REFCNT를 사용하여 액세스 할 수 있습니다). 이것이 매크로인 이유는 배치(layout)를 추상화하고 디버그 빌드에서 추가 필드를 활성화하기 위한 것입니다.

참고

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 = "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으로 설정합니다. 형이 pydocpickle 모듈과 호환되도록 하려면 실제 점으로 구분된 임포트 경로를 사용하는 것이 중요합니다.

.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 = "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 = "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, lastnumber의 세 가지 데이터 어트리뷰트가 있습니다. firstlast 변수는 이름과 성을 포함하는 파이썬 문자열입니다. 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 처리기를 사용하여 firstlast 어트리뷰트를 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보다 크다는 것을 확실히 알고 있을 때;

  • 객체의 할당 해제가 GIL을 해제하지도 않고 형의 코드를 다시 호출하지도 않음을 알고 있을 때 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)

firstlast 멤버가 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 예제에서 firstlast 어트리뷰트가 설정되는 방식을 더 세밀하게 제어합니다. 이전 버전의 모듈에서는, 인스턴스 변수 firstlast를 문자열이 아닌 값으로 설정하거나 삭제할 수도 있습니다. 이 어트리뷰트들에 항상 문자열이 포함되도록 하고 싶습니다.

#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 = "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;
}

firstlast 어트리뷰트를 더 효과적으로 제어하기 위해, 사용자 정의 게터(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;
}

이러한 변경을 통해, firstlast 멤버가 절대 NULL이 아니라고 확신할 수 있어서, 거의 모든 경우에 NULL 값 검사를 제거할 수 있습니다. 이것은 대부분의 Py_XDECREF() 호출이 Py_DECREF() 호출로 변환될 수 있음을 의미합니다. 이러한 호출을 변경할 수 없는 유일한 장소는 tp_dealloc 구현에서인데, tp_new에서 이 멤버의 초기화가 실패했을 가능성이 있습니다.

또한 이전과 마찬가지로, 초기화 함수에서 모듈 초기화 함수와 모듈 이름을 바꾸고, setup.py 파일에 추가 정의를 추가합니다.

2.4. 순환 가비지 수거 지원하기

파이썬에는 참조 횟수가 0이 아닐 때도 불필요한 객체를 식별할 수 있는 순환 가비지 수거기 (GC)가 있습니다. 이것은 객체가 순환에 참여할 때 일어날 수 있습니다. 예를 들어, 다음을 고려하십시오:

>>> l = []
>>> l.append(l)
>>> del l

이 예에서, 자신을 포함하는 리스트를 만듭니다. 삭제해도 여전히 자체 참조가 있습니다. 참조 횟수가 0으로 떨어지지 않습니다. 다행스럽게도, 파이썬의 순환 가비지 수거기는 결국 리스트가 가비지임을 확인하고 해제합니다.

Custom 예제의 두 번째 버전에서는, 모든 종류의 객체를 firstlast 어트리뷰트에 저장할 수 있었습니다 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 = "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 구현에서 인자 이름을 visitarg로 정확하게 지정해야 합니다.

둘째, 순환에 참여할 수 있는 서브 객체를 지우는 메서드를 제공해야 합니다:

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 = "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_newtp_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 서브 클래스를 전달할 수 있어서 여전히 참조 순환을 만들 수 있습니다.