2. Xác định các loại tiện ích mở rộng: Hướng dẫn

Python cho phép người viết mô-đun mở rộng C xác định các loại mới có thể được thao tác từ mã Python, giống như các loại strlist tích hợp sẵn. Mã cho tất cả các loại tiện ích mở rộng đều tuân theo một mẫu nhưng có một số chi tiết bạn cần hiểu trước khi có thể bắt đầu. Tài liệu này là một sự giới thiệu nhẹ nhàng về chủ đề này.

2.1. Khái niệm cơ bản

Thời gian chạy CPython xem tất cả các đối tượng Python dưới dạng các biến thuộc loại PyObject*, đóng vai trò là "loại cơ sở" cho tất cả các đối tượng Python. Bản thân cấu trúc PyObject chỉ chứa reference count của đối tượng và một con trỏ tới "đối tượng loại" của đối tượng. Đây là nơi diễn ra hành động; đối tượng kiểu xác định hàm (C) nào được trình thông dịch gọi khi, chẳng hạn, khi một thuộc tính được tra cứu trên một đối tượng, một phương thức được gọi hoặc nó được nhân với một đối tượng khác. Các hàm C này được gọi là "phương thức kiểu".

Vì vậy, nếu bạn muốn xác định loại tiện ích mở rộng mới, bạn cần tạo một đối tượng loại mới.

Loại điều này chỉ có thể được giải thích bằng ví dụ, vì vậy đây là một mô-đun tối thiểu nhưng đầy đủ xác định một loại mới có tên Custom bên trong mô-đun mở rộng C custom:

Ghi chú

Những gì chúng tôi đang trình bày ở đây là cách xác định loại tiện ích mở rộng static truyền thống. Nó phải đủ cho hầu hết các mục đích sử dụng. C API cũng cho phép xác định các loại tiện ích mở rộng được phân bổ heap bằng cách sử dụng hàm PyType_FromSpec(), chức năng này không được đề cập trong hướng dẫn này.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

cấu trúc typedef {
    PyObject_HEAD
    /* Các trường dành riêng cho loại sẽ xuất hiện ở đây. */
} Đối tượng tùy chỉnh;

PyTypeObject CustomType tĩnh = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

int tĩnh
custom_module_exec(PyObject *m)
{
    if (PyType_Ready(&CustomType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        trả về -1;
    }

    trả về 0;
}

tĩnh PyModuleDef_Slot custom_module_slots[] = {
    {Py_mod_exec, custom_module_exec},
    // Chỉ cần sử dụng cái này khi sử dụng kiểu tĩnh
    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
    {0, NULL}
};

tĩnh PyModuleDef custom_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "tùy chỉnh",
    .m_doc = "Mô-đun mẫu tạo kiểu tiện ích mở rộng.",
    .m_size = 0,
    .m_slots = custom_module_slots,
};

PyMODINIT_FUNC
PyInit_custom(void)
{
    trả về PyModuleDef_Init(&custom_module);
}

Bây giờ có khá nhiều thứ để tiếp thu ngay lập tức, nhưng hy vọng các chi tiết sẽ có vẻ quen thuộc với chương trước. Tệp này xác định ba điều:

  1. Custom object chứa những gì: đây là cấu trúc CustomObject, được phân bổ một lần cho mỗi phiên bản Custom.

  2. Cách hoạt động của Custom type: đây là cấu trúc CustomType, xác định một tập hợp các cờ và con trỏ hàm mà trình thông dịch sẽ kiểm tra khi các thao tác cụ thể được yêu cầu.

  3. Cách xác định và thực thi mô-đun custom: đây là hàm PyInit_custom và cấu trúc custom_module liên quan để xác định mô-đun và hàm custom_module_exec để thiết lập một đối tượng mô-đun mới.

Bit đầu tiên là:

cấu trúc typedef {
    PyObject_HEAD
} Đối tượng tùy chỉnh;

Đây là những gì một đối tượng Tùy chỉnh sẽ chứa. PyObject_HEAD là bắt buộc khi bắt đầu mỗi cấu trúc đối tượng và xác định một trường có tên ob_base thuộc loại PyObject, chứa một con trỏ tới một đối tượng loại và số lượng tham chiếu (những thứ này có thể được truy cập bằng cách sử dụng macro Py_TYPEPy_REFCNT tương ứng). Lý do của macro là để trừu tượng hóa bố cục và kích hoạt các trường bổ sung trong debug builds.

Ghi chú

Không có dấu chấm phẩy ở trên sau macro PyObject_HEAD. Hãy cảnh giác với việc vô tình thêm một cái: một số trình biên dịch sẽ phàn nàn.

Tất nhiên, các đối tượng thường lưu trữ dữ liệu bổ sung ngoài bản tóm tắt PyObject_HEAD tiêu chuẩn; ví dụ: đây là định nghĩa cho float Python tiêu chuẩn:

cấu trúc typedef {
    PyObject_HEAD
    gấp đôi ob_fval;
} PyFloatObject;

Bit thứ hai là định nghĩa của đối tượng kiểu.

PyTypeObject CustomType tĩnh = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

Ghi chú

Chúng tôi khuyên bạn nên sử dụng các công cụ khởi tạo được chỉ định theo kiểu C99 như trên, để tránh liệt kê tất cả các trường PyTypeObject mà bạn không quan tâm và cũng tránh quan tâm đến thứ tự khai báo của các trường.

Định nghĩa thực tế của PyTypeObject trong object.h có nhiều fields hơn định nghĩa ở trên. Các trường còn lại sẽ được trình biên dịch C điền bằng các số 0 và thông thường là không chỉ định chúng một cách rõ ràng trừ khi bạn cần chúng.

Chúng ta sẽ tách nó ra, từng trường một:

.ob_base = PyVarObject_HEAD_INIT(NULL, 0)

Dòng này là bản soạn sẵn bắt buộc để khởi tạo trường ob_base được đề cập ở trên.

.tp_name = "custom.Custom",

Tên loại của chúng tôi. Điều này sẽ xuất hiện trong biểu diễn văn bản mặc định của các đối tượng của chúng tôi và trong một số thông báo lỗi, ví dụ:

>>> "" + custom.Custom()
Traceback (cuộc gọi gần đây nhất):
  Tệp "<stdin>", dòng 1, trong <module>
TypeError: chỉ có thể nối str (không phải "custom.Custom") với str

Lưu ý rằng tên là tên có dấu chấm bao gồm cả tên mô-đun và tên loại trong mô-đun. Mô-đun trong trường hợp này là custom và loại là Custom, vì vậy chúng tôi đặt tên loại thành custom.Custom. Việc sử dụng đường dẫn nhập có dấu chấm thực là điều quan trọng để làm cho kiểu của bạn tương thích với các mô-đun pydocpickle.

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

Điều này là để Python biết cần phân bổ bao nhiêu bộ nhớ khi tạo các phiên bản Custom mới. tp_itemsize chỉ được sử dụng cho các đối tượng có kích thước thay đổi và nếu không thì bằng 0.

Ghi chú

Nếu bạn muốn kiểu của mình có thể được phân lớp từ Python và kiểu của bạn có cùng tp_basicsize với kiểu cơ sở, bạn có thể gặp vấn đề với đa kế thừa. Một lớp con Python thuộc loại của bạn sẽ phải liệt kê loại của bạn trước tiên trong __bases__, nếu không nó sẽ không thể gọi phương thức __new__() của loại của bạn mà không gặp lỗi. Bạn có thể tránh vấn đề này bằng cách đảm bảo rằng loại của bạn có giá trị tp_basicsize lớn hơn loại cơ sở của nó. Trong hầu hết các trường hợp, điều này dù sao cũng đúng vì loại cơ sở của bạn sẽ là object hoặc nếu không bạn sẽ thêm thành viên dữ liệu vào loại cơ sở của mình và do đó sẽ tăng kích thước của nó.

Chúng tôi đặt cờ lớp thành Py_TPFLAGS_DEFAULT.

.tp_flags = Py_TPFLAGS_DEFAULT,

Tất cả các loại nên bao gồm hằng số này trong cờ của chúng. Nó cho phép tất cả các thành viên được xác định cho đến ít nhất là Python 3.3. Nếu bạn cần thêm thành viên, bạn sẽ cần HOẶC các cờ tương ứng.

Chúng tôi cung cấp chuỗi tài liệu cho loại trong tp_doc.

.tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),

Để cho phép tạo đối tượng, chúng tôi phải cung cấp trình xử lý tp_new. Điều này tương đương với phương thức __new__() của Python, nhưng phải được chỉ định rõ ràng. Trong trường hợp này, chúng ta chỉ có thể sử dụng cách triển khai mặc định được cung cấp bởi hàm API PyType_GenericNew().

.tp_new = PyType_GenericNew,

Mọi thứ khác trong tệp đều quen thuộc, ngoại trừ một số mã trong custom_module_exec():

if (PyType_Ready(&CustomType) < 0) {
    trả về -1;
}

Thao tác này khởi tạo loại Custom, điền một số thành viên vào các giá trị mặc định thích hợp, bao gồm ob_type mà ban đầu chúng tôi đặt thành NULL.

if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
    trả về -1;
}

Điều này thêm loại vào từ điển mô-đun. Điều này cho phép chúng ta tạo các phiên bản Custom bằng cách gọi lớp Custom:

>>> nhập khẩu tùy chỉnh
>>> mycustom = custom.Custom()

Thế thôi! Tất cả những gì còn lại là xây dựng nó; đặt đoạn mã trên vào một tệp có tên custom.c,

[xây dựng hệ thống]
yêu cầu = ["setuptools"]
build-backend = "setuptools.build_meta"

[dự án]
tên = "tùy chỉnh"
phiên bản = "1"

trong một tệp có tên pyproject.toml

từ setuptools nhập Tiện ích mở rộng, thiết lập
setup(ext_modules=[Extension("custom", ["custom.c"])])

trong một tệp có tên setup.py; sau đó gõ

$ python -m cài đặt pip.

trong shell sẽ tạo một tệp custom.so trong thư mục con và cài đặt nó; bây giờ hãy kích hoạt Python --- bạn sẽ có thể import custom và chơi đùa với các đối tượng Custom.

Điều đó không quá khó phải không?

Tất nhiên, loại Tùy chỉnh hiện tại khá nhàm chán. Nó không có dữ liệu và không làm gì cả. Nó thậm chí không thể được phân lớp.

2.2. Thêm dữ liệu và phương thức vào ví dụ Cơ bản

Hãy mở rộng ví dụ cơ bản để thêm một số dữ liệu và phương thức. Hãy làm cho kiểu này có thể sử dụng được như một lớp cơ sở. Chúng tôi sẽ tạo một mô-đun mới, custom2 để bổ sung các khả năng sau:

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

cấu trúc typedef {
    PyObject_HEAD
    Tên PyObject *first; /* */
    Họ PyObject *last;  /* */
    số int;
} Đối tượng tùy chỉnh;

khoảng trống tĩnh
Custom_dealloc(PyObject *op)
{
    CustomObject *self = (CustomObject *) op;
    Py_XDECREF(tự->đầu tiên);
    Py_XDECREF(tự->cuối cùng);
    Py_TYPE(tự)->tp_free(tự);
}

PyObject tĩnh *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *tự;
    self = (CustomObject *) type->tp_alloc(type, 0);
    nếu (tự != NULL) {
        tự->đầu tiên = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (self->first == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->cuối cùng = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (self->last == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->số = 0;
    }
    tự trả về (PyObject *);
}

int tĩnh
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    CustomObject *self = (CustomObject *) op;
    char tĩnh *kwlist[] = {"đầu tiên", "cuối cùng", "số", NULL};
    PyObject *first = NULL, *last = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &đầu tiên, &cuối cùng,
                                     &tự->số))
        trả về -1;

    nếu (đầu tiên) {
        Py_XSETREF(self->first, Py_NewRef(first));
    }
    nếu (cuối cùng) {
        Py_XSETREF(self->last, Py_NewRef(last));
    }
    trả về 0;
}

tĩnh PyMemberDef Custom_members[] = {
    {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "tên"},
    {"cuối cùng", Py_T_OBJECT_EX, offsetof(CustomObject, cuối cùng), 0,
     "họ"},
    {"số", Py_T_INT, offsetof(CustomObject, number), 0,
     "số tùy chỉnh"},
    {NULL} /* Lính canh */
};

PyObject tĩnh *
Custom_name(PyObject *op, PyObject *Py_UNUSED(giả))
{
    CustomObject *self = (CustomObject *) op;
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributionError, "đầu tiên");
        trả về NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributionError, "cuối cùng");
        trả về NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

PyMethodDef tĩnh Custom_methods[] = {
    {"tên", Custom_name, METH_NOARGS,
     "Trả lại tên, kết hợp họ và tên"
    },
    {NULL} /* Lính gác */
};

PyTypeObject CustomType tĩnh = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom2.Custom",
    .tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Tùy chỉnh_new,
    .tp_init = Tùy chỉnh_init,
    .tp_dealloc = Custom_dealloc,
    .tp_members = Thành viên tùy chỉnh,
    .tp_methods = Phương thức tùy chỉnh,
};

int tĩnh
custom_module_exec(PyObject *m)
{
    if (PyType_Ready(&CustomType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        trả về -1;
    }

    trả về 0;
}

tĩnh PyModuleDef_Slot custom_module_slots[] = {
    {Py_mod_exec, custom_module_exec},
    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
    {0, NULL}
};

tĩnh PyModuleDef custom_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "custom2",
    .m_doc = "Mô-đun mẫu tạo kiểu tiện ích mở rộng.",
    .m_size = 0,
    .m_slots = custom_module_slots,
};

PyMODINIT_FUNC
PyInit_custom2(void)
{
    trả về PyModuleDef_Init(&custom_module);
}

Phiên bản này của mô-đun có một số thay đổi.

Loại Custom hiện có ba thuộc tính dữ liệu trong cấu trúc C, first, lastnumber. Các biến firstlast là các chuỗi Python chứa họ và tên. Thuộc tính number là số nguyên C.

Cấu trúc đối tượng được cập nhật tương ứng:

cấu trúc typedef {
    PyObject_HEAD
    Tên PyObject *first; /* */
    Họ PyObject *last;  /* */
    số int;
} Đối tượng tùy chỉnh;

Bởi vì hiện tại chúng ta có dữ liệu cần quản lý nên chúng ta phải cẩn thận hơn trong việc phân bổ và giải phóng đối tượng. Tối thiểu, chúng ta cần một phương thức phân bổ

khoảng trống tĩnh
Custom_dealloc(PyObject *op)
{
    CustomObject *self = (CustomObject *) op;
    Py_XDECREF(tự->đầu tiên);
    Py_XDECREF(tự->cuối cùng);
    Py_TYPE(tự)->tp_free(tự);
}

được gán cho thành viên tp_dealloc:

.tp_dealloc = Custom_dealloc,

Phương pháp này trước tiên xóa số lượng tham chiếu của hai thuộc tính Python. Py_XDECREF() xử lý chính xác trường hợp đối số của nó là NULL (điều này có thể xảy ra ở đây nếu tp_new bị lỗi giữa chừng). Sau đó, nó gọi thành viên tp_free thuộc loại của đối tượng (được tính bởi Py_TYPE(self)) để giải phóng bộ nhớ của đối tượng. Lưu ý rằng loại đối tượng có thể không phải là CustomType, vì đối tượng có thể là một thể hiện của lớp con.

Ghi chú

Việc truyền rõ ràng tới CustomObject * ở trên là cần thiết vì chúng tôi đã xác định Custom_dealloc để nhận đối số PyObject *, vì con trỏ hàm tp_dealloc mong đợi nhận được đối số PyObject *. Bằng cách gán cho một loại vị trí tp_dealloc, chúng tôi tuyên bố rằng nó chỉ có thể được gọi với các phiên bản của lớp CustomObject của chúng tôi, vì vậy việc truyền tới (CustomObject *) là an toàn. Đây là tính đa hình hướng đối tượng, trong C!

Trong mã hiện có hoặc trong các phiên bản trước của hướng dẫn này, bạn có thể thấy các hàm tương tự lấy con trỏ trực tiếp đến cấu trúc đối tượng kiểu con (CustomObject*), như sau:

Custom_dealloc(CustomObject *self)
{
    Py_XDECREF(tự->đầu tiên);
    Py_XDECREF(tự->cuối cùng);
    Py_TYPE(self)->tp_free((PyObject *) self);
}
...
.tp_dealloc = (hàm hủy) Custom_dealloc,

Điều này thực hiện tương tự trên tất cả các kiến ​​trúc mà CPython hỗ trợ, nhưng theo tiêu chuẩn C, nó kích hoạt hành vi không xác định.

Chúng tôi muốn đảm bảo rằng họ và tên được khởi tạo thành chuỗi trống, vì vậy chúng tôi cung cấp triển khai tp_new

PyObject tĩnh *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *tự;
    self = (CustomObject *) type->tp_alloc(type, 0);
    nếu (tự != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        self->last = PyUnicode_FromString("");
        if (tự->cuối == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->số = 0;
    }
    tự trả về (PyObject *);
}

và cài đặt nó trong thành viên tp_new:

.tp_new = Tùy chỉnh_new,

Trình xử lý tp_new chịu trách nhiệm tạo (ngược lại với việc khởi tạo) các đối tượng thuộc loại. Nó được hiển thị bằng Python dưới dạng phương thức __new__(). Không bắt buộc phải xác định thành viên tp_new và thực sự nhiều loại tiện ích mở rộng sẽ chỉ sử dụng lại PyType_GenericNew() như được thực hiện trong phiên bản đầu tiên của loại Custom ở trên. Trong trường hợp này, chúng tôi sử dụng trình xử lý tp_new để khởi tạo các thuộc tính firstlast thành các giá trị mặc định không phải NULL.

tp_new được truyền vào loại đang được khởi tạo (không nhất thiết phải là CustomType, nếu một lớp con được khởi tạo) và bất kỳ đối số nào được truyền khi loại được gọi và dự kiến ​​sẽ trả về phiên bản đã tạo. Trình xử lý tp_new luôn chấp nhận các đối số vị trí và từ khóa, nhưng chúng thường bỏ qua các đối số đó, để lại việc xử lý đối số cho các phương thức khởi tạo (còn gọi là tp_init trong C hoặc __init__ trong Python).

Ghi chú

tp_new không nên gọi tp_init một cách rõ ràng vì trình thông dịch sẽ tự thực hiện việc đó.

Việc triển khai tp_new gọi khe tp_alloc để phân bổ bộ nhớ:

self = (CustomObject *) type->tp_alloc(type, 0);

Vì việc cấp phát bộ nhớ có thể không thành công nên chúng tôi phải kiểm tra kết quả tp_alloc với NULL trước khi tiếp tục.

Ghi chú

Chúng tôi không tự mình điền vào chỗ trống tp_alloc. Thay vào đó, PyType_Ready() sẽ điền nó cho chúng ta bằng cách kế thừa nó từ lớp cơ sở của chúng ta, lớp này mặc định là object. Hầu hết các loại sử dụng chiến lược phân bổ mặc định.

Ghi chú

Nếu bạn đang tạo một tp_new hợp tác (một tp_new hoặc __new__() của loại cơ sở), bạn phải not cố gắng xác định phương thức nào sẽ gọi bằng cách sử dụng thứ tự phân giải phương thức trong thời gian chạy. Luôn xác định tĩnh loại bạn sẽ gọi và gọi trực tiếp tp_new hoặc qua type->tp_base->tp_new. Nếu bạn không làm điều này, các lớp con Python thuộc loại của bạn cũng kế thừa từ các lớp do Python xác định khác có thể không hoạt động chính xác. (Cụ thể, bạn không thể tạo phiên bản của các lớp con như vậy nếu không nhận được TypeError.)

Chúng tôi cũng xác định hàm khởi tạo chấp nhận các đối số để cung cấp các giá trị ban đầu cho phiên bản của chúng tôi:

int tĩnh
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    CustomObject *self = (CustomObject *) op;
    char tĩnh *kwlist[] = {"đầu tiên", "cuối cùng", "số", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &đầu tiên, &cuối cùng,
                                     &tự->số))
        trả về -1;

    nếu (đầu tiên) {
        tmp = tự->đầu tiên;
        Py_INCREF(đầu tiên);
        tự->đầu tiên = đầu tiên;
        Py_XDECREF(tmp);
    }
    nếu (cuối cùng) {
        tmp = tự->cuối cùng;
        Py_INCREF(cuối cùng);
        tự->cuối cùng = cuối cùng;
        Py_XDECREF(tmp);
    }
    trả về 0;
}

bằng cách lấp đầy khe tp_init.

.tp_init = Tùy chỉnh_init,

Khe tp_init được hiển thị trong Python dưới dạng phương thức __init__(). Nó được sử dụng để khởi tạo một đối tượng sau khi nó được tạo. Trình khởi tạo luôn chấp nhận các đối số từ khóa và vị trí, đồng thời chúng sẽ trả về 0 nếu thành công hoặc -1 nếu có lỗi.

Không giống như trình xử lý tp_new, không có gì đảm bảo rằng tp_init sẽ được gọi (ví dụ: mô-đun pickle theo mặc định không gọi __init__() trên các phiên bản chưa được chọn). Nó cũng có thể được gọi nhiều lần. Bất kỳ ai cũng có thể gọi phương thức __init__() trên đối tượng của chúng tôi. Vì lý do này, chúng ta phải hết sức cẩn thận khi gán các giá trị thuộc tính mới. Ví dụ: chúng tôi có thể muốn chỉ định thành viên first như thế này:

nếu (đầu tiên) {
    Py_XDECREF(tự->đầu tiên);
    Py_INCREF(đầu tiên);
    tự->đầu tiên = đầu tiên;
}

Nhưng điều này sẽ có rủi ro. Loại của chúng tôi không hạn chế loại thành viên first, vì vậy nó có thể là bất kỳ loại đối tượng nào. Nó có thể có một hàm hủy khiến mã được thực thi để cố truy cập thành viên first; hoặc hàm hủy đó có thể tách thread state và để mã tùy ý chạy trong các luồng khác truy cập và sửa đổi đối tượng của chúng ta.

Để hoang tưởng và tự bảo vệ mình trước khả năng này, chúng tôi hầu như luôn phân công lại các thành viên trước khi giảm số lượng tham chiếu của họ. Khi nào chúng ta không phải làm điều này?

  • khi chúng tôi hoàn toàn biết rằng số tham chiếu lớn hơn 1;

  • khi chúng tôi biết rằng việc hủy phân bổ đối tượng [1] sẽ không tách rời thread state cũng như không gây ra bất kỳ lệnh gọi nào trở lại mã loại của chúng tôi;

  • khi giảm số lượng tham chiếu trong trình xử lý tp_dealloc trên loại không hỗ trợ thu thập rác theo chu kỳ [2].

Chúng tôi muốn hiển thị các biến thể hiện của mình dưới dạng thuộc tính. Có một số cách để làm điều đó. Cách đơn giản nhất là xác định định nghĩa thành viên:

tĩnh PyMemberDef Custom_members[] = {
    {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "tên"},
    {"cuối cùng", Py_T_OBJECT_EX, offsetof(CustomObject, cuối cùng), 0,
     "họ"},
    {"số", Py_T_INT, offsetof(CustomObject, number), 0,
     "số tùy chỉnh"},
    {NULL} /* Lính gác */
};

và đặt các định nghĩa vào khe tp_members

.tp_members = Thành viên tùy chỉnh,

Mỗi định nghĩa thành viên có tên thành viên, loại, offset, cờ truy cập và chuỗi tài liệu. Xem phần Quản lý thuộc tính chung bên dưới để biết chi tiết.

Một nhược điểm của phương pháp này là nó không cung cấp cách hạn chế các loại đối tượng có thể được gán cho thuộc tính Python. Chúng tôi mong đợi họ và tên là chuỗi nhưng mọi đối tượng Python đều có thể được chỉ định. Hơn nữa, các thuộc tính có thể bị xóa bằng cách đặt con trỏ C thành NULL. Mặc dù chúng tôi có thể đảm bảo rằng các thành viên được khởi tạo thành giá trị không phải NULL, nhưng các thành viên có thể được đặt thành NULL nếu các thuộc tính bị xóa.

Chúng tôi xác định một phương thức duy nhất, Custom.name(), xuất ra tên đối tượng dưới dạng nối giữa họ và tên.

PyObject tĩnh *
Custom_name(PyObject *op, PyObject *Py_UNUSED(giả))
{
    CustomObject *self = (CustomObject *) op;
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributionError, "đầu tiên");
        trả về NULL;
    }
    if (tự->cuối == NULL) {
        PyErr_SetString(PyExc_AttributionError, "cuối cùng");
        trả về NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

Phương thức này được triển khai dưới dạng hàm C lấy phiên bản Custom (hoặc lớp con Custom) làm đối số đầu tiên. Các phương thức luôn lấy một thể hiện làm đối số đầu tiên. Các phương thức cũng thường lấy các đối số vị trí và từ khóa, nhưng trong trường hợp này, chúng ta không lấy bất kỳ đối số nào và không cần chấp nhận một bộ đối số vị trí hoặc từ điển đối số từ khóa. Phương thức này tương đương với phương thức Python:

tên def (tự):
    trả về "%s %s" % (self.first, self.last)

Lưu ý rằng chúng tôi phải kiểm tra khả năng các thành viên firstlast của chúng tôi là NULL. Điều này là do chúng có thể bị xóa, trong trường hợp đó chúng được đặt thành NULL. Sẽ tốt hơn nếu ngăn chặn việc xóa các thuộc tính này và hạn chế các giá trị thuộc tính ở dạng chuỗi. Chúng ta sẽ xem cách thực hiện điều đó trong phần tiếp theo.

Bây giờ chúng ta đã xác định được phương thức, chúng ta cần tạo một mảng các định nghĩa phương thức:

PyMethodDef tĩnh Custom_methods[] = {
    {"tên", Custom_name, METH_NOARGS,
     "Trả lại tên, kết hợp họ và tên"
    },
    {NULL} /* Lính gác */
};

(lưu ý rằng chúng tôi đã sử dụng cờ METH_NOARGS để chỉ ra rằng phương thức này không mong đợi đối số nào ngoài self)

và gán nó vào khe tp_methods:

.tp_methods = Phương thức tùy chỉnh,

Cuối cùng, chúng ta sẽ làm cho kiểu của chúng ta có thể sử dụng được như một lớp cơ sở cho việc phân lớp con. Chúng tôi đã viết các phương thức của mình một cách cẩn thận cho đến nay để chúng không đưa ra bất kỳ giả định nào về loại đối tượng được tạo hoặc sử dụng, vì vậy tất cả những gì chúng tôi cần làm là thêm Py_TPFLAGS_BASETYPE vào định nghĩa cờ lớp:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

Chúng tôi đổi tên PyInit_custom() thành PyInit_custom2(), cập nhật tên mô-đun trong cấu trúc PyModuleDef và cập nhật tên lớp đầy đủ trong cấu trúc PyTypeObject.

Cuối cùng, chúng tôi cập nhật tệp setup.py để bao gồm mô-đun mới,

từ setuptools nhập Tiện ích mở rộng, thiết lập
thiết lập(ext_modules=[
    Tiện ích mở rộng ("tùy chỉnh", ["custom.c"]),
    Tiện ích mở rộng ("custom2", ["custom2.c"]),
])

và sau đó chúng tôi cài đặt lại để có thể import custom2:

$ python -m cài đặt pip.

2.3. Cung cấp khả năng kiểm soát tốt hơn đối với các thuộc tính dữ liệu

Trong phần này, chúng tôi sẽ cung cấp khả năng kiểm soát tốt hơn về cách đặt thuộc tính firstlast trong ví dụ Custom. Trong phiên bản trước của mô-đun của chúng tôi, các biến thể hiện firstlast có thể được đặt thành giá trị không phải chuỗi hoặc thậm chí bị xóa. Chúng tôi muốn đảm bảo rằng các thuộc tính này luôn chứa chuỗi.

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

cấu trúc typedef {
    PyObject_HEAD
    Tên PyObject *first; /* */
    Họ PyObject *last;  /* */
    số int;
} Đối tượng tùy chỉnh;

khoảng trống tĩnh
Custom_dealloc(PyObject *op)
{
    CustomObject *self = (CustomObject *) op;
    Py_XDECREF(tự->đầu tiên);
    Py_XDECREF(tự->cuối cùng);
    Py_TYPE(tự)->tp_free(tự);
}

PyObject tĩnh *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *tự;
    self = (CustomObject *) type->tp_alloc(type, 0);
    nếu (tự != NULL) {
        tự->đầu tiên = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (self->first == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->cuối cùng = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (tự->cuối == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->số = 0;
    }
    tự trả về (PyObject *);
}

int tĩnh
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    CustomObject *self = (CustomObject *) op;
    char tĩnh *kwlist[] = {"đầu tiên", "cuối cùng", "số", NULL};
    PyObject *first = NULL, *last = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &đầu tiên, &cuối cùng,
                                     &tự->số))
        trả về -1;

    nếu (đầu tiên) {
        Py_SETREF(self->first, Py_NewRef(first));
    }
    nếu (cuối cùng) {
        Py_SETREF(self->last, Py_NewRef(last));
    }
    trả về 0;
}

tĩnh PyMemberDef Custom_members[] = {
    {"số", Py_T_INT, offsetof(CustomObject, number), 0,
     "số tùy chỉnh"},
    {NULL} /* Lính gác */
};

PyObject tĩnh *
Custom_getfirst(PyObject *op, void *closure)
{
    CustomObject *self = (CustomObject *) op;
    return Py_NewRef(self->first);
}

int tĩnh
Custom_setfirst(PyObject *op, PyObject *value, void *đóng)
{
    CustomObject *self = (CustomObject *) op;
    nếu (giá trị == NULL) {
        PyErr_SetString(PyExc_TypeError, "Không thể xóa thuộc tính đầu tiên");
        trả về -1;
    }
    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "Giá trị thuộc tính đầu tiên phải là một chuỗi");
        trả về -1;
    }
    Py_SETREF(self->first, Py_NewRef(value));
    trả về 0;
}

PyObject tĩnh *
Custom_getlast(PyObject *op, void *closure)
{
    CustomObject *self = (CustomObject *) op;
    return Py_NewRef(self->last);
}

int tĩnh
Custom_setlast(PyObject *op, PyObject *value, void *đóng)
{
    CustomObject *self = (CustomObject *) op;
    nếu (giá trị == NULL) {
        PyErr_SetString(PyExc_TypeError, "Không thể xóa thuộc tính cuối cùng");
        trả về -1;
    }
    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "Giá trị thuộc tính cuối cùng phải là một chuỗi");
        trả về -1;
    }
    Py_SETREF(self->last, Py_NewRef(value));
    trả về 0;
}

PyGetSetDef tĩnh Custom_getsetters[] = {
    {"đầu tiên", Custom_getfirst, Custom_setfirst,
     "tên", NULL},
    {"cuối cùng", Custom_getlast, Custom_setlast,
     "họ", NULL},
    {NULL} /* Lính canh */
};

PyObject tĩnh *
Custom_name(PyObject *op, PyObject *Py_UNUSED(giả))
{
    CustomObject *self = (CustomObject *) op;
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

PyMethodDef tĩnh Custom_methods[] = {
    {"tên", Custom_name, METH_NOARGS,
     "Trả lại tên, kết hợp họ và tên"
    },
    {NULL} /* Lính gác */
};

PyTypeObject CustomType tĩnh = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom3.Custom",
    .tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Tùy chỉnh_new,
    .tp_init = Tùy chỉnh_init,
    .tp_dealloc = Custom_dealloc,
    .tp_members = Thành viên tùy chỉnh,
    .tp_methods = Phương thức tùy chỉnh,
    .tp_getset = Custom_getsetters,
};

int tĩnh
custom_module_exec(PyObject *m)
{
    if (PyType_Ready(&CustomType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        trả về -1;
    }

    trả về 0;
}

tĩnh PyModuleDef_Slot custom_module_slots[] = {
    {Py_mod_exec, custom_module_exec},
    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
    {0, NULL}
};

tĩnh PyModuleDef custom_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "tùy chỉnh3",
    .m_doc = "Mô-đun mẫu tạo kiểu tiện ích mở rộng.",
    .m_size = 0,
    .m_slots = custom_module_slots,
};

PyMODINIT_FUNC
PyInit_custom3(void)
{
    trả về PyModuleDef_Init(&custom_module);
}

Để cung cấp khả năng kiểm soát tốt hơn, đối với các thuộc tính firstlast, chúng tôi sẽ sử dụng các hàm getter và setter tùy chỉnh. Dưới đây là các chức năng để nhận và thiết lập thuộc tính first

PyObject tĩnh *
Custom_getfirst(PyObject *op, void *closure)
{
    CustomObject *self = (CustomObject *) op;
    Py_INCREF(tự->đầu tiên);
    tự trả về-> trước;
}

int tĩnh
Custom_setfirst(PyObject *op, PyObject *value, void *đóng)
{
    CustomObject *self = (CustomObject *) op;
    PyObject *tmp;
    nếu (giá trị == NULL) {
        PyErr_SetString(PyExc_TypeError, "Không thể xóa thuộc tính đầu tiên");
        trả về -1;
    }
    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "Giá trị thuộc tính đầu tiên phải là một chuỗi");
        trả về -1;
    }
    tmp = tự->đầu tiên;
    Py_INCREF(giá trị);
    tự->đầu tiên = giá trị;
    Py_DECREF(tmp);
    trả về 0;
}

Hàm getter được truyền một đối tượng Custom và một "closure", là một con trỏ void. Trong trường hợp này, việc đóng cửa bị bỏ qua. (Việc đóng hỗ trợ cách sử dụng nâng cao trong đó dữ liệu định nghĩa được chuyển đến getter và setter. Ví dụ: điều này có thể được sử dụng để cho phép một tập hợp các hàm getter và setter duy nhất quyết định thuộc tính nhận hoặc đặt dựa trên dữ liệu trong bao đóng.)

Hàm setter được truyền đối tượng Custom, giá trị mới và phần đóng. Giá trị mới có thể là NULL, trong trường hợp đó thuộc tính sẽ bị xóa. Trong setter của chúng tôi, chúng tôi sẽ báo lỗi nếu thuộc tính bị xóa hoặc nếu giá trị mới của nó không phải là một chuỗi.

Chúng tôi tạo ra một loạt các cấu trúc PyGetSetDef:

PyGetSetDef tĩnh Custom_getsetters[] = {
    {"đầu tiên", Custom_getfirst, Custom_setfirst,
     "tên", NULL},
    {"cuối cùng", Custom_getlast, Custom_setlast,
     "họ", NULL},
    {NULL} /* Lính gác */
};

và đăng ký nó vào slot tp_getset:

.tp_getset = Custom_getsetters,

Mục cuối cùng trong cấu trúc PyGetSetDef là "closure" được đề cập ở trên. Trong trường hợp này, chúng tôi không sử dụng bao đóng, vì vậy chúng tôi chỉ chuyển NULL.

Chúng tôi cũng xóa định nghĩa thành viên cho các thuộc tính này:

tĩnh PyMemberDef Custom_members[] = {
    {"số", Py_T_INT, offsetof(CustomObject, number), 0,
     "số tùy chỉnh"},
    {NULL} /* Lính gác */
};

Chúng tôi cũng cần cập nhật trình xử lý tp_init để chỉ cho phép các chuỗi [3] được truyền:

int tĩnh
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    CustomObject *self = (CustomObject *) op;
    char tĩnh *kwlist[] = {"đầu tiên", "cuối cùng", "số", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &đầu tiên, &cuối cùng,
                                     &tự->số))
        trả về -1;

    nếu (đầu tiên) {
        tmp = tự->đầu tiên;
        Py_INCREF(đầu tiên);
        tự->đầu tiên = đầu tiên;
        Py_DECREF(tmp);
    }
    nếu (cuối cùng) {
        tmp = tự->cuối cùng;
        Py_INCREF(cuối cùng);
        tự->cuối cùng = cuối cùng;
        Py_DECREF(tmp);
    }
    trả về 0;
}

Với những thay đổi này, chúng tôi có thể đảm bảo rằng các thành viên firstlast không bao giờ là NULL nên chúng tôi có thể xóa kiểm tra giá trị NULL trong hầu hết các trường hợp. Điều này có nghĩa là hầu hết các cuộc gọi Py_XDECREF() có thể được chuyển đổi thành các cuộc gọi Py_DECREF(). Nơi duy nhất chúng tôi không thể thay đổi các lệnh gọi này là trong quá trình triển khai tp_dealloc, nơi có khả năng việc khởi tạo các thành viên này không thành công trong tp_new.

Chúng tôi cũng đổi tên hàm khởi tạo mô-đun và tên mô-đun trong hàm khởi tạo, như chúng tôi đã làm trước đây và chúng tôi thêm định nghĩa bổ sung vào tệp setup.py.

2.4. Hỗ trợ thu gom rác theo chu kỳ

Python có cyclic garbage collector (GC) có thể xác định các đối tượng không cần thiết ngay cả khi số tham chiếu của chúng không bằng 0. Điều này có thể xảy ra khi các đối tượng tham gia vào các chu kỳ. Ví dụ, hãy xem xét:

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

Trong ví dụ này, chúng ta tạo một danh sách chứa chính nó. Khi chúng tôi xóa nó, nó vẫn có một tham chiếu từ chính nó. Số tham chiếu của nó không giảm xuống 0. May mắn thay, trình thu gom rác tuần hoàn của Python cuối cùng sẽ phát hiện ra rằng danh sách này là rác và giải phóng nó.

Trong phiên bản thứ hai của ví dụ Custom, chúng tôi đã cho phép bất kỳ loại đối tượng nào được lưu trữ trong thuộc tính first hoặc last [4]. Ngoài ra, ở phiên bản thứ hai và thứ ba, chúng tôi đã cho phép phân lớp Custom và các lớp con có thể thêm các thuộc tính tùy ý. Vì bất kỳ lý do nào trong hai lý do đó, các đối tượng Custom có thể tham gia vào các chu kỳ:

>>> nhập tùy chỉnh3
>>> lớp phái sinh(custom3.Custom): vượt qua
...
>>> n = Dẫn xuất()
>>> n.some_attribute = n

Để cho phép một phiên bản Custom tham gia vào chu trình tham chiếu được GC tuần hoàn phát hiện và thu thập đúng cách, loại Custom của chúng tôi cần điền vào hai vị trí bổ sung và bật cờ cho phép các vị trí này:

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

cấu trúc typedef {
    PyObject_HEAD
    Tên PyObject *first; /* */
    Họ PyObject *last;  /* */
    số int;
} Đối tượng tùy chỉnh;

int tĩnh
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
    CustomObject *self = (CustomObject *) op;
    Py_VISIT(tự->đầu tiên);
    Py_VISIT(tự->cuối cùng);
    trả về 0;
}

int tĩnh
Custom_clear(PyObject *op)
{
    CustomObject *self = (CustomObject *) op;
    Py_CLEAR(tự->đầu tiên);
    Py_CLEAR(tự->cuối cùng);
    trả về 0;
}

khoảng trống tĩnh
Custom_dealloc(PyObject *op)
{
    PyObject_GC_UnTrack(op);
    (void)Custom_clear(op);
    Py_TYPE(op)->tp_free(op);
}

PyObject tĩnh *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *tự;
    self = (CustomObject *) type->tp_alloc(type, 0);
    nếu (tự != NULL) {
        tự->đầu tiên = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (self->first == NULL) {
            Py_DECREF(tự);
            trả về NULL;
        }
        tự->cuối cùng = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
        if (self->last == NULL) {
            Py_DECREF(tự);
            trả lại NULL;
        }
        tự->số = 0;
    }
    tự trả về (PyObject *);
}

int tĩnh
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    CustomObject *self = (CustomObject *) op;
    char tĩnh *kwlist[] = {"đầu tiên", "cuối cùng", "số", NULL};
    PyObject *first = NULL, *last = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &đầu tiên, &cuối cùng,
                                     &tự->số))
        trả về -1;

    nếu (đầu tiên) {
        Py_SETREF(self->first, Py_NewRef(first));
    }
    nếu (cuối cùng) {
        Py_SETREF(self->last, Py_NewRef(last));
    }
    trả về 0;
}

tĩnh PyMemberDef Custom_members[] = {
    {"số", Py_T_INT, offsetof(CustomObject, number), 0,
     "số tùy chỉnh"},
    {NULL} /* Lính canh */
};

PyObject tĩnh *
Custom_getfirst(PyObject *op, void *closure)
{
    CustomObject *self = (CustomObject *) op;
    return Py_NewRef(self->first);
}

int tĩnh
Custom_setfirst(PyObject *op, PyObject *value, void *đóng)
{
    CustomObject *self = (CustomObject *) op;
    nếu (giá trị == NULL) {
        PyErr_SetString(PyExc_TypeError, "Không thể xóa thuộc tính đầu tiên");
        trả về -1;
    }
    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "Giá trị thuộc tính đầu tiên phải là một chuỗi");
        trả về -1;
    }
    Py_XSETREF(self->first, Py_NewRef(value));
    trả về 0;
}

PyObject tĩnh *
Custom_getlast(PyObject *op, void *closure)
{
    CustomObject *self = (CustomObject *) op;
    return Py_NewRef(self->last);
}

int tĩnh
Custom_setlast(PyObject *op, PyObject *value, void *đóng)
{
    CustomObject *self = (CustomObject *) op;
    nếu (giá trị == NULL) {
        PyErr_SetString(PyExc_TypeError, "Không thể xóa thuộc tính cuối cùng");
        trả về -1;
    }
    if (! PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "Giá trị thuộc tính cuối cùng phải là một chuỗi");
        trả về -1;
    }
    Py_XSETREF(self->last, Py_NewRef(value));
    trả về 0;
}

PyGetSetDef tĩnh Custom_getsetters[] = {
    {"đầu tiên", Custom_getfirst, Custom_setfirst,
     "tên", NULL},
    {"cuối cùng", Custom_getlast, Custom_setlast,
     "họ", NULL},
    {NULL} /* Lính gác */
};

PyObject tĩnh *
Custom_name(PyObject *op, PyObject *Py_UNUSED(giả))
{
    CustomObject *self = (CustomObject *) op;
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

PyMethodDef tĩnh Custom_methods[] = {
    {"tên", Custom_name, METH_NOARGS,
     "Trả lại tên, kết hợp họ và tên"
    },
    {NULL} /* Lính gác */
};

PyTypeObject CustomType tĩnh = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom4.Custom",
    .tp_doc = PyDoc_STR("Đối tượng tùy chỉnh"),
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_new = Tùy chỉnh_new,
    .tp_init = Tùy chỉnh_init,
    .tp_dealloc = Custom_dealloc,
    .tp_traverse = Tùy chỉnh_traverse,
    .tp_clear = Tùy chỉnh_clear,
    .tp_members = Thành viên tùy chỉnh,
    .tp_methods = Phương thức tùy chỉnh,
    .tp_getset = Custom_getsetters,
};

int tĩnh
custom_module_exec(PyObject *m)
{
    if (PyType_Ready(&CustomType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
        trả về -1;
    }

    trả về 0;
}

tĩnh PyModuleDef_Slot custom_module_slots[] = {
    {Py_mod_exec, custom_module_exec},
    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
    {0, NULL}
};

tĩnh PyModuleDef custom_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "custom4",
    .m_doc = "Mô-đun mẫu tạo kiểu tiện ích mở rộng.",
    .m_size = 0,
    .m_slots = custom_module_slots,
};

PyMODINIT_FUNC
PyInit_custom4(void)
{
    trả về PyModuleDef_Init(&custom_module);
}

Đầu tiên, phương pháp truyền tải cho phép GC tuần hoàn biết về các đối tượng con có thể tham gia vào các chu trình

int tĩnh
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
    CustomObject *self = (CustomObject *) op;
    int vret;
    if (tự->đầu tiên) {
        vret = thăm(self->first, arg);
        nếu (vret != 0)
            trả lại vret;
    }
    if (tự->cuối cùng) {
        vret = thăm(self->cuối cùng, arg);
        nếu (vret != 0)
            trả lại vret;
    }
    trả về 0;
}

Đối với mỗi tiểu dự án có thể tham gia vào các chu trình, chúng ta cần gọi hàm visit(), hàm này được truyền cho phương thức truyền tải. Hàm visit() lấy đối số là đối tượng con và đối số bổ sung arg được truyền cho phương thức truyền tải. Nó trả về một giá trị số nguyên phải được trả về nếu nó khác 0.

Python cung cấp macro Py_VISIT() để tự động gọi các hàm truy cập. Với Py_VISIT(), chúng ta có thể giảm thiểu số lượng mẫu soạn sẵn trong Custom_traverse:

int tĩnh
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
    CustomObject *self = (CustomObject *) op;
    Py_VISIT(tự->đầu tiên);
    Py_VISIT(tự->cuối cùng);
    trả về 0;
}

Ghi chú

Việc triển khai tp_traverse phải đặt tên chính xác cho các đối số của nó là visitarg để sử dụng Py_VISIT().

Thứ hai, chúng ta cần cung cấp một phương thức để xóa mọi đối tượng con có thể tham gia vào các chu kỳ

int tĩnh
Custom_clear(PyObject *op)
{
    CustomObject *self = (CustomObject *) op;
    Py_CLEAR(tự->đầu tiên);
    Py_CLEAR(tự->cuối cùng);
    trả về 0;
}

Lưu ý việc sử dụng macro Py_CLEAR(). Đó là cách được khuyến nghị và an toàn để xóa thuộc tính dữ liệu của các loại tùy ý trong khi giảm số lượng tham chiếu của chúng. Thay vào đó, nếu bạn gọi Py_XDECREF() trên thuộc tính trước khi đặt nó thành NULL, thì có khả năng hàm hủy của thuộc tính sẽ gọi lại thành mã đọc lại thuộc tính (especially nếu có chu trình tham chiếu).

Ghi chú

Bạn có thể mô phỏng Py_CLEAR() bằng cách viết:

PyObject *tmp;
tmp = tự->đầu tiên;
tự->đầu tiên = NULL;
Py_XDECREF(tmp);

Tuy nhiên, sẽ dễ dàng hơn và ít xảy ra lỗi hơn khi luôn sử dụng Py_CLEAR() khi xóa một thuộc tính. Đừng cố gắng tối ưu hóa vi mô mà phải đánh đổi bằng sự mạnh mẽ!

Bộ giải mã Custom_dealloc có thể gọi mã tùy ý khi xóa thuộc tính. Nó có nghĩa là GC tròn có thể được kích hoạt bên trong hàm. Vì GC giả định số lượng tham chiếu không bằng 0 nên chúng ta cần hủy theo dõi đối tượng khỏi GC bằng cách gọi PyObject_GC_UnTrack() trước khi xóa thành viên. Đây là bộ giải mã được triển khai lại của chúng tôi bằng cách sử dụng PyObject_GC_UnTrack()Custom_clear:

khoảng trống tĩnh
Custom_dealloc(PyObject *op)
{
    PyObject_GC_UnTrack(op);
    (void)Custom_clear(op);
    Py_TYPE(op)->tp_free(op);
}

Cuối cùng, chúng ta thêm cờ Py_TPFLAGS_HAVE_GC vào cờ lớp:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

Đó là khá nhiều nó. Nếu chúng tôi đã viết các trình xử lý tp_alloc hoặc tp_free tùy chỉnh, chúng tôi cần sửa đổi chúng để thu thập rác theo chu kỳ. Hầu hết các tiện ích mở rộng sẽ sử dụng các phiên bản được cung cấp tự động.

2.5. Phân loại các loại khác

Có thể tạo các loại tiện ích mở rộng mới có nguồn gốc từ các loại hiện có. Dễ dàng kế thừa nhất từ ​​các loại được tích hợp sẵn vì tiện ích mở rộng có thể dễ dàng sử dụng PyTypeObject mà nó cần. Có thể khó chia sẻ các cấu trúc PyTypeObject này giữa các mô-đun mở rộng.

Trong ví dụ này, chúng tôi sẽ tạo loại SubList kế thừa từ loại list tích hợp sẵn. Loại mới sẽ hoàn toàn tương thích với các danh sách thông thường, nhưng sẽ có thêm một phương thức increment() giúp tăng bộ đếm nội bộ:

>>> nhập danh sách phụ
>>> 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>

cấu trúc typedef {
    Danh sách PyListObject;
    trạng thái int;
} SubListObject;

PyObject tĩnh *
SubList_increment(PyObject *op, PyObject *Py_UNUSED(giả))
{
    SubListObject *self = (SubListObject *) op;
    tự->trạng thái++;
    trả về PyLong_FromLong (tự->trạng thái);
}

tĩnh PyMethodDef SubList_methods[] = {
    {"tăng", SubList_increment, METH_NOARGS,
     PyDoc_STR("bộ đếm trạng thái tăng")},
    {NULL},
};

int tĩnh
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    SubListObject *self = (SubListObject *) op;
    if (PyList_Type.tp_init(op, args, kwds) < 0)
        trả về -1;
    tự->trạng thái = 0;
    trả về 0;
}

tĩnh PyTypeObject SubListType = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "danh sách con.SubList",
    .tp_doc = PyDoc_STR("Đối tượng danh sách con"),
    .tp_basicsize = sizeof(SubListObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_init = Danh sách con_init,
    .tp_methods = SubList_methods,
};

int tĩnh
sublist_module_exec(PyObject *m)
{
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
        trả về -1;
    }

    trả về 0;
}

tĩnh PyModuleDef_Slot sublist_module_slots[] = {
    {Py_mod_exec, danh sách con_module_exec},
    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
    {0, NULL}
};

sublist_module PyModuleDef tĩnh = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "danh sách phụ",
    .m_doc = "Mô-đun mẫu tạo kiểu tiện ích mở rộng.",
    .m_size = 0,
    .m_slots = danh sách phụ_module_slots,
};

PyMODINIT_FUNC
PyInit_sublist(void)
{
    trả về PyModuleDef_Init(&sublist_module);
}

Như bạn có thể thấy, mã nguồn gần giống với các ví dụ Custom trong các phần trước. Chúng tôi sẽ chia nhỏ sự khác biệt chính giữa chúng.

cấu trúc typedef {
    Danh sách PyListObject;
    trạng thái int;
} SubListObject;

Sự khác biệt chính đối với các đối tượng kiểu dẫn xuất là cấu trúc đối tượng của kiểu cơ sở phải là giá trị đầu tiên. Loại cơ sở sẽ bao gồm PyObject_HEAD() ở đầu cấu trúc của nó.

Khi một đối tượng Python là một phiên bản SubList, con trỏ PyObject * của nó có thể được truyền an toàn tới cả PyListObject *SubListObject *:

int tĩnh
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
    SubListObject *self = (SubListObject *) op;
    if (PyList_Type.tp_init(op, args, kwds) < 0)
        trả về -1;
    tự->trạng thái = 0;
    trả về 0;
}

Chúng ta thấy ở trên cách gọi phương thức __init__() của loại cơ sở.

Mẫu này rất quan trọng khi viết một kiểu với các thành viên tp_newtp_dealloc tùy chỉnh. Trình xử lý tp_new không thực sự tạo bộ nhớ cho đối tượng bằng tp_alloc mà hãy để lớp cơ sở xử lý nó bằng cách gọi tp_new của chính nó.

Cấu trúc PyTypeObject hỗ trợ tp_base chỉ định lớp cơ sở cụ thể của loại. Do các vấn đề về trình biên dịch đa nền tảng, bạn không thể điền trực tiếp vào trường đó bằng tham chiếu đến PyList_Type; việc này nên được thực hiện trong hàm Py_mod_exec:

int tĩnh
sublist_module_exec(PyObject *m)
{
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0) {
        trả về -1;
    }

    if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
        trả về -1;
    }

    trả về 0;
}

Trước khi gọi PyType_Ready(), cấu trúc loại phải điền vào vị trí tp_base. Khi chúng ta tạo ra một loại hiện có, không cần thiết phải điền vào vị trí tp_alloc bằng PyType_GenericNew() -- chức năng phân bổ từ loại cơ sở sẽ được kế thừa.

Sau đó, việc gọi PyType_Ready() và thêm đối tượng loại vào mô-đun cũng giống như các ví dụ Custom cơ bản.

Chú thích cuối trang