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

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

## 2.1. 기초

The CPython runtime sees all Python objects as variables of type PyObject*, which serves as a “base type” for all Python objects. The `PyObject` structure itself only contains the object’s reference count and a pointer to the object’s “type object”. This is where the action is; the type object determines which (C) functions get called by the interpreter when, for instance, an attribute gets looked up on an object, a method called, or it is multiplied by another object. These C functions are called “type methods”.

따라서, 새 확장형을 정의하려면, 새 형 객체를 만들어야 합니다.

This sort of thing can only be explained by example, so here’s a minimal, but complete, module that defines a new type named `Custom` inside a C extension module `custom`:

참고

여기에 표시하는 것은 정적인(static) 확장형을 정의하는 전통적인 방법입니다. 대부분의 용도에 적합해야 합니다. C API는 또한 `PyType_FromSpec()` 함수를 사용하여 힙 할당 확장형을 정의 할 수 있습니다만, 이 자습서에서는 다루지 않습니다.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>

    typedef struct {
        PyObject_HEAD
        /* Type-specific fields go here. */
    } CustomObject;

    static PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        // Just use this while using static types
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

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

1. What a `Custom` object contains: this is the `CustomObject` struct, which is allocated once for each `Custom` instance.

2. How the `Custom` type behaves: this is the `CustomType` struct, which defines a set of flags and function pointers that the interpreter inspects when specific operations are requested.

3. How to define and execute the `custom` module: this is the `PyInit_custom` function and the associated `custom_module` struct for defining the module, and the `custom_module_exec` function to set up a fresh module object.

첫 번째 것은:

    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 = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
    };

참고

신경 쓰지 않는 모든 `PyTypeObject` 필드를 나열하지 않고 필드의 선언 순서를 신경 쓰지 않으려면, 위와 같이 C99 스타일의 지명(designated) 초기화자를 사용하는 것이 좋습니다.

`object.h`에 있는 `PyTypeObject`의 실제 정의는 위의 정의보다 더 많은 필드를 갖습니다. 나머지 필드는 C 컴파일러에 의해 0으로 채워지며, 필요하지 않으면 명시적으로 지정하지 않는 것이 일반적입니다.

한 번에 한 필드씩 따로 다루려고 합니다:

    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)

이 줄은 위에서 언급한 `ob_base` 필드를 초기화하기 위한 필수 상용구입니다.

    .tp_name = "custom.Custom",

우리 형의 이름. 이것은 객체의 기본 텍스트 표현과 일부 에러 메시지에 나타납니다, 예를 들어:

    >>> "" + custom.Custom()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can only concatenate str (not "custom.Custom") to str

Note that the name is a dotted name that includes both the module name and the name of the type within the module. The module in this case is `custom` and the type is `Custom`, so we set the type name to `custom.Custom`. Using the real dotted import path is important to make your type compatible with the `pydoc` and `pickle` modules.

    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,

This is so that Python knows how much memory to allocate when creating new `Custom` instances. `tp_itemsize` is only used for variable-sized objects and should otherwise be zero.

참고

If you want your type to be subclassable from Python, and your type has the same `tp_basicsize` as its base type, you may have problems with multiple inheritance. A Python subclass of your type will have to list your type first in its `__bases__`, or else it will not be able to call your type’s `__new__()` method without getting an error. You can avoid this problem by ensuring that your type has a larger value for `tp_basicsize` than its base type does. Most of the time, this will be true anyway, because either your base type will be `object`, or else you will be adding data members to your base type, and therefore increasing its size.

We set the class flags to `Py_TPFLAGS_DEFAULT`.

    .tp_flags = Py_TPFLAGS_DEFAULT,

모든 형은 이 상수를 플래그에 포함해야 합니다. 적어도 파이썬 3.3까지 정의된 모든 멤버를 활성화합니다. 추가 멤버가 필요하면, 해당 플래그를 OR 해야 합니다.

`tp_doc`에 형의 독스트링을 제공합니다.

    .tp_doc = PyDoc_STR("Custom objects"),

To enable object creation, we have to provide a `tp_new` handler. This is the equivalent of the Python method `__new__()`, but has to be specified explicitly. In this case, we can just use the default implementation provided by the API function `PyType_GenericNew()`.

    .tp_new = PyType_GenericNew,

Everything else in the file should be familiar, except for some code in `custom_module_exec()`:

    if (PyType_Ready(&CustomType) < 0) {
        return -1;
    }

This initializes the `Custom` type, filling in a number of members to the appropriate default values, including `ob_type` that we initially set to `NULL`.

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        return -1;
    }

This adds the type to the module dictionary. This allows us to create `Custom` instances by calling the `Custom` class:

    >>> import custom
    >>> mycustom = custom.Custom()

That’s it! All that remains is to build it; put the above code in a file called `custom.c`,

    [build-system]
    requires = ["setuptools"]
    build-backend = "setuptools.build_meta"

    [project]
    name = "custom"
    version = "1"

in a file called `pyproject.toml`, and

    from setuptools import Extension, setup
    setup(ext_modules=[Extension("custom", ["custom.c"])])

를 `setup.py`라는 파일에 넣은 다음; 다음을

    $ python -m pip install .

in a shell should produce a file `custom.so` in a subdirectory and install it; now fire up Python — you should be able to `import custom` and play around with `Custom` objects.

그렇게 어렵지 않습니다, 그렇지 않나요?

물론, 현재 Custom 형은 그리 흥미롭지 않습니다. 데이터가 없고 아무것도 하지 않습니다. 서브 클래싱조차 할 수 없습니다.

## 2.2. 기초 예제에 데이터와 메서드 추가하기

Let’s extend the basic example to add some data and methods. Let’s also make the type usable as a base class. We’ll create a new module, `custom2` that adds these capabilities:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    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;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_XSETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_XSETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
         "first name"},
        {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
         "last name"},
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_name(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 = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom2.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_new = Custom_new,
        .tp_init = (initproc) Custom_init,
        .tp_dealloc = (destructor) Custom_dealloc,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom2",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom2(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

이 버전의 모듈에는 여러 가지 변경 사항이 있습니다.

The `Custom` type now has three data attributes in its C struct, first, last, and number. The first and last variables are Python strings containing first and last names. The number attribute is a C integer.

객체 구조체는 다음과 같이 갱신됩니다:

    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,

This method first clears the reference counts of the two Python attributes. `Py_XDECREF()` correctly handles the case where its argument is `NULL` (which might happen here if `tp_new` failed midway). It then calls the `tp_free` member of the object’s type (computed by `Py_TYPE(self)`) to free the object’s memory. Note that the object’s type might not be `CustomType`, because the object may be an instance of a subclass.

참고

`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,

The `tp_new` handler is responsible for creating (as opposed to initializing) objects of the type. It is exposed in Python as the `__new__()` method. It is not required to define a `tp_new` member, and indeed many extension types will simply reuse `PyType_GenericNew()` as done in the first version of the `Custom` type above. In this case, we use the `tp_new` handler to initialize the `first` and `last` attributes to non-`NULL` default values.

`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`입니다)에서 상속하여 이를 채웁니다. 대부분의 형은 기본 할당 전략을 사용합니다.

참고

If you are creating a co-operative `tp_new` (one that calls a base type’s `tp_new` or `__new__()`), you must not try to determine what method to call using method resolution order at runtime. Always statically determine what type you are going to call, and call its `tp_new` directly, or via `type->tp_base->tp_new`. If you do not do this, Python subclasses of your type that also inherit from other Python-defined classes may not work correctly. (Specifically, you may not be able to create instances of such subclasses without getting a `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,

The `tp_init` slot is exposed in Python as the `__init__()` method. It is used to initialize an object after it’s created. Initializers always accept positional and keyword arguments, and they should return either `0` on success or `-1` on error.

Unlike the `tp_new` handler, there is no guarantee that `tp_init` is called at all (for example, the `pickle` module by default doesn’t call `__init__()` on unpickled instances). It can also be called multiple times. Anyone can call the `__init__()` method on our objects. For this reason, we have to be extra careful when assigning the new attribute values. We might be tempted, for example to assign the `first` member like this:

    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", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
         "first name"},
        {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
         "last name"},
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

그리고 `tp_members` 슬롯에 정의를 넣습니다:

    .tp_members = Custom_members,

각 멤버 정의에는 멤버 이름, 형, 오프셋, 액세스 플래그 및 독스트링이 있습니다. 자세한 내용은 아래 범용 어트리뷰트 관리 섹션을 참조하십시오.

이 접근법의 단점은 파이썬 어트리뷰트에 대입할 수 있는 객체의 형을 제한할 방법을 제공하지 않는다는 것입니다. 이름과 성은 문자열일 것으로 기대하지만, 모든 파이썬 객체를 할당할 수 있습니다. 또한 어트리뷰트를 삭제할 수 있습니다, C 포인터를 `NULL`로 설정합니다. `NULL`이 아닌 값으로 멤버를 초기화 할 수 있지만, 어트리뷰트를 삭제하면 멤버를 `NULL`로 설정할 수 있습니다.

We define a single method, `Custom.name()`, that outputs the objects name as the concatenation of the first and last names.

    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);
    }

The method is implemented as a C function that takes a `Custom` (or `Custom` subclass) instance as the first argument. Methods always take an instance as the first argument. Methods often take positional and keyword arguments as well, but in this case we don’t take any and don’t need to accept a positional argument tuple or keyword argument dictionary. This method is equivalent to the Python method:

    def name(self):
        return "%s %s" % (self.first, self.last)

Note that we have to check for the possibility that our `first` and `last` members are `NULL`. This is because they can be deleted, in which case they are set to `NULL`. It would be better to prevent deletion of these attributes and to restrict the attribute values to be strings. We’ll see how to do that in the next section.

이제 메서드를 정의했습니다, 메서드 정의 배열을 만들어야 합니다:

    static PyMethodDef Custom_methods[] = {
        {"name", (PyCFunction) Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

(note that we used the `METH_NOARGS` flag to indicate that the method is expecting no arguments other than self)

그리고 `tp_methods` 슬롯에 대입합니다:

    .tp_methods = Custom_methods,

Finally, we’ll make our type usable as a base class for subclassing. We’ve written our methods carefully so far so that they don’t make any assumptions about the type of the object being created or used, so all we need to do is to add the `Py_TPFLAGS_BASETYPE` to our class flag definition:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

We rename `PyInit_custom()` to `PyInit_custom2()`, update the module name in the `PyModuleDef` struct, and update the full class name in the `PyTypeObject` struct.

Finally, we update our `setup.py` file to include the new module,

    from setuptools import Extension, setup
    setup(ext_modules=[
        Extension("custom", ["custom.c"]),
        Extension("custom2", ["custom2.c"]),
    ])

and then we re-install so that we can `import custom2`:

    $ python -m pip install .

## 2.3. 데이터 어트리뷰트를 더 세밀하게 제어하기

In this section, we’ll provide finer control over how the `first` and `last` attributes are set in the `Custom` example. In the previous version of our module, the instance variables `first` and `last` could be set to non-string values or even deleted. We want to make sure that these attributes always contain strings.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    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;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_SETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_SETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_getfirst(CustomObject *self, void *closure)
    {
        return Py_NewRef(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_SETREF(self->first, Py_NewRef(value));
        return 0;
    }

    static PyObject *
    Custom_getlast(CustomObject *self, void *closure)
    {
        return Py_NewRef(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_SETREF(self->last, Py_NewRef(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 = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom3.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_new = Custom_new,
        .tp_init = (initproc) Custom_init,
        .tp_dealloc = (destructor) Custom_dealloc,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
        .tp_getset = Custom_getsetters,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom3",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom3(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

To provide greater control, over the `first` and `last` attributes, we’ll use custom getter and setter functions. Here are the functions for getting and setting the `first` attribute:

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

The getter function is passed a `Custom` object and a “closure”, which is a void pointer. In this case, the closure is ignored. (The closure supports an advanced usage in which definition data is passed to the getter and setter. This could, for example, be used to allow a single set of getter and setter functions that decide the attribute to get or set based on data in the closure.)

The setter function is passed the `Custom` object, the new value, and the closure. The new value may be `NULL`, in which case the attribute is being deleted. In our setter, we raise an error if the attribute is deleted or if its new value is not a string.

`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", Py_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으로 떨어지지 않습니다. 다행스럽게도, 파이썬의 순환 가비지 수거기는 결국 리스트가 가비지임을 확인하고 해제합니다.

In the second version of the `Custom` example, we allowed any kind of object to be stored in the `first` or `last` attributes [4]. Besides, in the second and third versions, we allowed subclassing `Custom`, and subclasses may add arbitrary attributes. For any of those two reasons, `Custom` objects can participate in cycles:

    >>> import custom3
    >>> class Derived(custom3.Custom): pass
    ...
    >>> n = Derived()
    >>> n.some_attribute = n

To allow a `Custom` instance participating in a reference cycle to be properly detected and collected by the cyclic GC, our `Custom` type needs to fill two additional slots and to enable a flag that enables these slots:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    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;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_SETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_SETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_getfirst(CustomObject *self, void *closure)
    {
        return Py_NewRef(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_XSETREF(self->first, Py_NewRef(value));
        return 0;
    }

    static PyObject *
    Custom_getlast(CustomObject *self, void *closure)
    {
        return Py_NewRef(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_XSETREF(self->last, Py_NewRef(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 = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom4.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
        .tp_new = Custom_new,
        .tp_init = (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 int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom4",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom4(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

첫째, 탐색(traversal) 메서드는 순환 GC가 순환에 참여할 수 있는 서브 객체에 대해 알 수 있도록 합니다:

    static int
    Custom_traverse(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;
    }

For each subobject that can participate in cycles, we need to call the `visit()` function, which is passed to the traversal method. The `visit()` function takes as arguments the subobject and the extra argument arg passed to the traversal method. It returns an integer value that must be returned if it is non-zero.

파이썬은 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);
    }

Finally, we add the `Py_TPFLAGS_HAVE_GC` flag to the class flags:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

거의 다 됐습니다. 사용자 정의 `tp_alloc`이나 `tp_free` 처리기를 작성했으면, 순환 가비지 수거를 위해 이를 수정해야 합니다. 대부분의 확장은 자동으로 제공된 버전을 사용합니다.

## 2.5. 다른 형의 서브 클래싱

기존 형에서 파생된 새 확장형을 만들 수 있습니다. 확장이 필요한 `PyTypeObject`를 쉽게 사용할 수 있어서, 내장형에서 상속하기가 가장 쉽습니다. 확장 모듈 간에 이러한 `PyTypeObject` 구조체를 공유하기 어려울 수 있습니다.

In this example we will create a `SubList` type that inherits from the built-in `list` type. The new type will be completely compatible with regular lists, but will have an additional `increment()` method that increases an internal counter:

    >>> 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 = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "sublist.SubList",
        .tp_doc = PyDoc_STR("SubList objects"),
        .tp_basicsize = sizeof(SubListObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_init = (initproc) SubList_init,
        .tp_methods = SubList_methods,
    };

    static int
    sublist_module_exec(PyObject *m)
    {
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot sublist_module_slots[] = {
        {Py_mod_exec, sublist_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef sublist_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "sublist",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = sublist_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_sublist(void)
    {
        return PyModuleDef_Init(&sublist_module);
    }

As you can see, the source code closely resembles the `Custom` examples in previous sections. We will break down the main differences between them.

    typedef struct {
        PyListObject list;
        int state;
    } SubListObject;

파생형 객체의 주요 차이점은 베이스형의 객체 구조체가 첫 번째 값이어야 한다는 것입니다. 베이스형은 이미 구조체의 시작 부분에 `PyObject_HEAD()`를 포함합니다.

When a Python object is a `SubList` instance, its `PyObject *` pointer can be safely cast to both `PyListObject *` and `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;
    }

We see above how to call through to the `__init__()` method of the base type.

이 패턴은 사용자 정의 `tp_new`와 `tp_dealloc` 멤버를 갖는 형을 작성할 때 중요합니다. `tp_new` 처리기는 실제로 `tp_alloc`을 사용하여 객체의 메모리를 만들지 말고, 베이스 클래스가 자체 `tp_new`를 호출하여 처리하도록 해야 합니다.

The `PyTypeObject` struct supports a `tp_base` specifying the type’s concrete base class. Due to cross-platform compiler issues, you can’t fill that field directly with a reference to `PyList_Type`; it should be done in the `Py_mod_exec` function:

    static int
    sublist_module_exec(PyObject *m)
    {
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
            return -1;
        }

        return 0;
    }

`PyType_Ready()`를 호출하기 전에, 형 구조체에 `tp_base` 슬롯이 채워져 있어야 합니다. 기존 형을 파생할 때, `PyType_GenericNew()`로 `tp_alloc` 슬롯을 채울 필요는 없습니다 – 베이스형의 할당 함수가 상속됩니다.

After that, calling `PyType_Ready()` and adding the type object to the module is the same as with the basic `Custom` examples.

각주

[1]

이것은 객체가 문자열이나 부동 소수점과 같은 기본형이라는 것을 알고 있을 때 참입니다.

[2]

우리의 형이 가비지 수거를 지원하지 않기 때문에, 이 예제에서는 `tp_dealloc` 처리기에서 이것을 사용했습니다.

[3]

우리는 이제 first와 last 멤버가 문자열이라는 것을 알고 있어서, 참조 횟수를 줄이는 데 덜 주의할 수 있지만, 우리는 문자열 서브 클래스의 인스턴스를 받아들입니다. 일반 문자열을 할당 해제하는 것이 우리 객체로 다시 호출되지는 않더라도, 문자열 서브 클래스의 인스턴스를 할당 해제하는 것이 객체로 다시 호출되지 않는다고 보장할 수 없습니다.

[4]

또한, 어트리뷰트가 문자열 인스턴스로 제한된 경우에도, 사용자는 임의의 `str` 서브 클래스를 전달할 수 있어서 여전히 참조 순환을 만들 수 있습니다.