# 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 = "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_TYPE`과 `Py_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`으로 설정합니다. 형이 `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 = "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, 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보다 크다는 것을 확실히 알고 있을 때;

- 객체의 할당 해제가 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)

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