Python C hiệu quả

Khá dễ dàng để thêm các mô-đun tích hợp mới vào Python, nếu bạn biết cách lập trình bằng C. Các mô-đun mở rộng như vậy có thể thực hiện hai việc không thể thực hiện trực tiếp trong Python. họ có thể triển khai các loại đối tượng tích hợp mới và họ có thể gọi các hàm thư viện C và các cuộc gọi hệ thống

Để hỗ trợ các tiện ích mở rộng, API Python (Giao diện lập trình viên ứng dụng) xác định một tập hợp các hàm, macro và biến cung cấp quyền truy cập vào hầu hết các khía cạnh của hệ thống thời gian chạy Python. API Python được tích hợp trong tệp nguồn C bằng cách bao gồm tiêu đề

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
5

Việc biên dịch mô-đun mở rộng phụ thuộc vào mục đích sử dụng cũng như thiết lập hệ thống của bạn;

Ghi chú

Giao diện tiện ích mở rộng C dành riêng cho CPython và các mô-đun tiện ích mở rộng không hoạt động trên các triển khai Python khác. Trong nhiều trường hợp, có thể tránh viết phần mở rộng C và duy trì tính di động cho các triển khai khác. Ví dụ: nếu trường hợp sử dụng của bạn đang gọi hàm thư viện C hoặc lệnh gọi hệ thống, bạn nên cân nhắc sử dụng mô-đun hoặc thư viện cffi thay vì viết mã C tùy chỉnh. Các mô-đun này cho phép bạn viết mã Python để giao tiếp với mã C và dễ di chuyển giữa các lần triển khai Python hơn là viết và biên dịch mô-đun mở rộng C

1. 1. Ví dụ đơn giản

Hãy tạo một mô-đun mở rộng có tên là

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
7 (món ăn yêu thích của những người hâm mộ Monty Python…) và giả sử chúng ta muốn tạo một giao diện Python cho chức năng thư viện C
PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
8. Hàm này lấy một chuỗi ký tự kết thúc null làm đối số và trả về một số nguyên. Chúng tôi muốn chức năng này có thể gọi được từ Python như sau

>>> import spam
>>> status = spam.system("ls -l")

Bắt đầu bằng cách tạo một tệp

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
9. (Trước đây, nếu một mô-đun được gọi là
PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
7, tệp C chứa phần triển khai của nó được gọi là
PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
9; nếu tên mô-đun rất dài, chẳng hạn như
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
2, thì tên mô-đun có thể chỉ là
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
3. )

Hai dòng đầu tiên của tệp của chúng tôi có thể là

#define PY_SSIZE_T_CLEAN
#include 

kéo theo API Python (bạn có thể thêm nhận xét mô tả mục đích của mô-đun và thông báo bản quyền nếu muốn)

Ghi chú

Vì Python có thể định nghĩa một số định nghĩa tiền xử lý ảnh hưởng đến tiêu đề tiêu chuẩn trên một số hệ thống, nên bạn phải bao gồm

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
4 trước bất kỳ tiêu đề tiêu chuẩn nào được đưa vào

Bạn nên luôn xác định

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
5 trước khi bao gồm
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
4. Xem mô tả về macro này

Tất cả các ký hiệu người dùng có thể nhìn thấy được xác định bởi

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
4 đều có tiền tố là
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
8 hoặc
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}
9, ngoại trừ những ký hiệu được xác định trong tệp tiêu đề tiêu chuẩn. Để thuận tiện và vì chúng được trình thông dịch Python sử dụng rộng rãi, nên
PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
5 bao gồm một số tệp tiêu đề tiêu chuẩn.
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
1,
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
2,
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
3 và
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
4. Nếu tệp tiêu đề thứ hai không tồn tại trên hệ thống của bạn, nó sẽ khai báo trực tiếp các hàm
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5,
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 và
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
7

Thứ tiếp theo chúng ta thêm vào tệp mô-đun của mình là hàm C sẽ được gọi khi biểu thức Python

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
8 được ước tính (chúng ta sẽ sớm xem nó được gọi như thế nào)

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}

Có một bản dịch đơn giản từ danh sách đối số trong Python (ví dụ: biểu thức đơn

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
9) sang các đối số được truyền cho hàm C. Hàm C luôn có hai đối số, được đặt tên theo quy ước là self và args

Đối số self trỏ đến đối tượng mô-đun cho các chức năng cấp mô-đun;

Đối số args sẽ là một con trỏ tới một đối tượng bộ Python chứa các đối số. Mỗi mục của bộ dữ liệu tương ứng với một đối số trong danh sách đối số của cuộc gọi. Các đối số là các đối tượng Python — để làm bất cứ điều gì với chúng trong hàm C của chúng ta, chúng ta phải chuyển đổi chúng thành các giá trị C. Hàm trong API Python kiểm tra các loại đối số và chuyển đổi chúng thành giá trị C. Nó sử dụng một chuỗi mẫu để xác định các loại đối số được yêu cầu cũng như các loại biến C để lưu trữ các giá trị được chuyển đổi. Thêm về điều này sau

trả về true (khác 0) nếu tất cả các đối số có đúng loại và các thành phần của nó đã được lưu trữ trong các biến có địa chỉ được truyền. Nó trả về false (không) nếu danh sách đối số không hợp lệ được thông qua. Trong trường hợp sau, nó cũng đưa ra một ngoại lệ thích hợp để hàm gọi có thể trả về

sts = system(command);
2 ngay lập tức (như chúng ta đã thấy trong ví dụ)

1. 2. Intermezzo. Lỗi và ngoại lệ

Một quy ước quan trọng xuyên suốt trình thông dịch Python là như sau. khi một chức năng bị lỗi, nó sẽ đặt một điều kiện ngoại lệ và trả về một giá trị lỗi (thường là con trỏ

sts = system(command);
3 hoặc
sts = system(command);
2). Thông tin ngoại lệ được lưu trữ trong ba thành viên của trạng thái luồng của trình thông dịch. Đây là
sts = system(command);
2 nếu không có ngoại lệ. Mặt khác, chúng tương đương với C của các thành viên của bộ Python được trả về bởi. Đây là loại ngoại lệ, trường hợp ngoại lệ và đối tượng truy nguyên. Điều quan trọng là phải biết về chúng để hiểu các lỗi được truyền xung quanh như thế nào

API Python xác định một số chức năng để đặt các loại ngoại lệ khác nhau

Một trong những phổ biến nhất là. Đối số của nó là một đối tượng ngoại lệ và một chuỗi C. Đối tượng ngoại lệ thường là một đối tượng được xác định trước như

sts = system(command);
8. Chuỗi C cho biết nguyên nhân gây ra lỗi và được chuyển đổi thành đối tượng chuỗi Python và được lưu trữ dưới dạng “giá trị được liên kết” của ngoại lệ

Một hàm hữu ích khác là , chỉ nhận một đối số ngoại lệ và xây dựng giá trị liên quan bằng cách kiểm tra biến toàn cục

return PyLong_FromLong(sts);
0. Hàm tổng quát nhất là , nhận hai đối số đối tượng, ngoại lệ và giá trị liên quan của nó. Bạn không cần các đối tượng được truyền cho bất kỳ chức năng nào trong số này

Bạn có thể kiểm tra không phá hủy xem một ngoại lệ đã được đặt với. Điều này trả về đối tượng ngoại lệ hiện tại hoặc

sts = system(command);
2 nếu không có ngoại lệ nào xảy ra. Thông thường, bạn không cần gọi để xem có lỗi xảy ra trong khi gọi hàm hay không, vì bạn có thể biết được từ giá trị trả về

Khi một hàm f gọi một hàm g khác phát hiện ra rằng hàm sau bị lỗi, f sẽ tự trả về một giá trị lỗi (thường là

sts = system(command);
2 hoặc
sts = system(command);
3). Nó không nên gọi một trong các hàm
return PyLong_FromLong(sts);
8 — một hàm đã được gọi bởi g. người gọi của f sau đó cũng được cho là trả lại một chỉ báo lỗi cho người gọi của nó, một lần nữa mà không gọi
return PyLong_FromLong(sts);
8, v.v. - nguyên nhân chi tiết nhất của lỗi đã được báo cáo bởi hàm đầu tiên phát hiện ra nó. Khi lỗi đến vòng lặp chính của trình thông dịch Python, điều này sẽ hủy bỏ mã Python hiện đang thực thi và cố gắng tìm một trình xử lý ngoại lệ do lập trình viên Python chỉ định

(Có những tình huống mà một mô-đun thực sự có thể đưa ra thông báo lỗi chi tiết hơn bằng cách gọi một hàm

return PyLong_FromLong(sts);
8 khác, và trong những trường hợp như vậy, bạn có thể làm như vậy. Tuy nhiên, theo nguyên tắc chung, điều này là không cần thiết và có thể làm mất thông tin về nguyên nhân gây ra lỗi. hầu hết các hoạt động có thể thất bại vì nhiều lý do. )

Để bỏ qua một ngoại lệ được đặt bởi một lệnh gọi hàm không thành công, điều kiện ngoại lệ phải được xóa rõ ràng bằng cách gọi. Lần duy nhất mã C nên gọi là nếu nó không muốn chuyển lỗi cho trình thông dịch mà muốn tự xử lý lỗi hoàn toàn (có thể bằng cách thử cách khác hoặc giả vờ không có gì sai)

Mỗi cuộc gọi

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 không thành công phải được chuyển thành một ngoại lệ — người gọi trực tiếp của
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 (hoặc
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
7) phải gọi và trả lại chính chỉ báo lỗi. Tất cả các chức năng tạo đối tượng (ví dụ, ) đã làm điều này, vì vậy lưu ý này chỉ liên quan đến những người gọi trực tiếp
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5

Cũng lưu ý rằng, ngoại trừ quan trọng là and friends, các hàm trả về trạng thái số nguyên thường trả về giá trị dương hoặc 0 nếu thành công và

sts = system(command);
3 nếu thất bại, giống như các cuộc gọi hệ thống Unix

Cuối cùng, hãy cẩn thận dọn sạch rác (bằng cách thực hiện hoặc gọi các đối tượng bạn đã tạo) khi bạn trả về một chỉ báo lỗi

Lựa chọn nâng cao ngoại lệ nào hoàn toàn là của bạn. Có các đối tượng C được khai báo trước tương ứng với tất cả các ngoại lệ Python tích hợp sẵn, chẳng hạn như

sts = system(command);
8, mà bạn có thể sử dụng trực tiếp. Tất nhiên, bạn nên chọn các trường hợp ngoại lệ một cách khôn ngoan — không sử dụng
#define PY_SSIZE_T_CLEAN
#include 
04 để có nghĩa là tệp không thể mở được (có lẽ nên là
#define PY_SSIZE_T_CLEAN
#include 
05). Nếu có gì đó không ổn với danh sách đối số, hàm thường tăng
#define PY_SSIZE_T_CLEAN
#include 
04. Nếu bạn có một đối số mà giá trị của nó phải nằm trong một phạm vi cụ thể hoặc phải đáp ứng các điều kiện khác, thì
#define PY_SSIZE_T_CLEAN
#include 
08 là phù hợp

Bạn cũng có thể xác định một ngoại lệ mới dành riêng cho mô-đun của mình. Đối với điều này, bạn thường khai báo một biến đối tượng tĩnh ở đầu tệp của mình

static PyObject *SpamError;

và khởi tạo nó trong chức năng khởi tạo mô-đun của bạn (_______9_______09) với một đối tượng ngoại lệ

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Lưu ý rằng tên Python cho đối tượng ngoại lệ là

#define PY_SSIZE_T_CLEAN
#include 
10. Hàm có thể tạo một lớp với lớp cơ sở là (trừ khi một lớp khác được chuyển vào thay vì
sts = system(command);
2), được mô tả trong

Cũng lưu ý rằng biến

#define PY_SSIZE_T_CLEAN
#include 
14 giữ lại tham chiếu đến lớp ngoại lệ mới được tạo; . Vì ngoại lệ có thể bị loại bỏ khỏi mô-đun bằng mã bên ngoài, nên cần có một tham chiếu sở hữu cho lớp để đảm bảo rằng nó sẽ không bị loại bỏ, khiến
#define PY_SSIZE_T_CLEAN
#include 
14 trở thành một con trỏ lơ lửng. Nếu nó trở thành một con trỏ lơ lửng, mã C làm tăng ngoại lệ có thể gây ra kết xuất lõi hoặc các tác dụng phụ ngoài ý muốn khác

Chúng tôi thảo luận về việc sử dụng

#define PY_SSIZE_T_CLEAN
#include 
16 làm kiểu trả về của hàm sau trong ví dụ này

Ngoại lệ

#define PY_SSIZE_T_CLEAN
#include 
10 có thể được đưa ra trong mô-đun mở rộng của bạn bằng cách sử dụng lệnh gọi tới như được hiển thị bên dưới

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}

1. 3. Quay lại ví dụ

Quay trở lại chức năng ví dụ của chúng tôi, bây giờ bạn có thể hiểu câu lệnh này

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;

Nó trả về

sts = system(command);
2 (chỉ báo lỗi cho các hàm trả về con trỏ đối tượng) nếu phát hiện thấy lỗi trong danh sách đối số, dựa trên ngoại lệ được đặt bởi. Mặt khác, giá trị chuỗi của đối số đã được sao chép vào biến cục bộ
#define PY_SSIZE_T_CLEAN
#include 
21. Đây là phép gán con trỏ và bạn không được phép sửa đổi chuỗi mà nó trỏ tới (vì vậy trong Tiêu chuẩn C, biến
#define PY_SSIZE_T_CLEAN
#include 
21 phải được khai báo đúng là
#define PY_SSIZE_T_CLEAN
#include 
23)

Câu lệnh tiếp theo là gọi hàm Unix

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
8, truyền cho nó chuỗi mà chúng ta vừa nhận được từ

sts = system(command);

Hàm

#define PY_SSIZE_T_CLEAN
#include 
26 của chúng tôi phải trả về giá trị của
#define PY_SSIZE_T_CLEAN
#include 
27 dưới dạng đối tượng Python. Điều này được thực hiện bằng cách sử dụng chức năng

return PyLong_FromLong(sts);

Trong trường hợp này, nó sẽ trả về một đối tượng số nguyên. (Có, số nguyên chẵn là đối tượng trên heap trong Python. )

Nếu bạn có một hàm C không trả về đối số hữu ích nào (hàm trả về void ), hàm Python tương ứng phải trả về

#define PY_SSIZE_T_CLEAN
#include 
29. Bạn cần thành ngữ này để làm như vậy (được triển khai bởi macro).

Py_INCREF(Py_None);
return Py_None;

là tên C của đối tượng Python đặc biệt

#define PY_SSIZE_T_CLEAN
#include 
29. Nó là một đối tượng Python chính hãng chứ không phải là một con trỏ
sts = system(command);
2, có nghĩa là "lỗi" trong hầu hết các ngữ cảnh, như chúng ta đã thấy

1. 4. Bảng phương thức và chức năng khởi tạo của mô-đun

Tôi đã hứa sẽ chỉ ra cách mà chương trình Python gọi ___9_______34. Đầu tiên, chúng ta cần liệt kê tên và địa chỉ của nó trong một “bảng phương thức”

#define PY_SSIZE_T_CLEAN
#include 
0

Lưu ý mục thứ ba (

#define PY_SSIZE_T_CLEAN
#include 
35). Đây là cờ báo cho trình thông dịch biết quy ước gọi sẽ được sử dụng cho hàm C. Nó thường phải luôn là
#define PY_SSIZE_T_CLEAN
#include 
35 hoặc
#define PY_SSIZE_T_CLEAN
#include 
37;

Khi chỉ sử dụng

#define PY_SSIZE_T_CLEAN
#include 
35, hàm sẽ yêu cầu các tham số cấp Python được chuyển vào dưới dạng một bộ có thể chấp nhận để phân tích cú pháp qua ;

Bit

#define PY_SSIZE_T_CLEAN
#include 
42 có thể được đặt trong trường thứ ba nếu các đối số từ khóa phải được chuyển đến hàm. Trong trường hợp này, hàm C sẽ chấp nhận tham số
#define PY_SSIZE_T_CLEAN
#include 
43 thứ ba sẽ là từ điển các từ khóa. Sử dụng để phân tích các đối số cho một chức năng như vậy

Bảng phương thức phải được tham chiếu trong cấu trúc định nghĩa mô-đun

#define PY_SSIZE_T_CLEAN
#include 
1

Đến lượt cấu trúc này phải được chuyển đến trình thông dịch trong chức năng khởi tạo của mô-đun. Hàm khởi tạo phải được đặt tên là

#define PY_SSIZE_T_CLEAN
#include 
45, trong đó tên là tên của mô-đun và phải là mục duy nhất không phải ____9_______46 được xác định trong tệp mô-đun

#define PY_SSIZE_T_CLEAN
#include 
2

Lưu ý rằng PyMODINIT_FUNC khai báo hàm là kiểu trả về

#define PY_SSIZE_T_CLEAN
#include 
43, khai báo mọi khai báo liên kết đặc biệt theo yêu cầu của nền tảng và đối với C++, khai báo hàm là
#define PY_SSIZE_T_CLEAN
#include 
48

Khi chương trình Python nhập mô-đun

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
7 lần đầu tiên,
#define PY_SSIZE_T_CLEAN
#include 
09 được gọi. (Xem bên dưới để biết nhận xét về việc nhúng Python. ) Nó gọi , trả về một đối tượng mô-đun và chèn các đối tượng hàm tích hợp vào mô-đun mới được tạo dựa trên bảng (một mảng cấu trúc) được tìm thấy trong định nghĩa mô-đun. trả về một con trỏ tới đối tượng mô-đun mà nó tạo ra. Nó có thể hủy bỏ với một lỗi nghiêm trọng đối với một số lỗi nhất định hoặc trả về
sts = system(command);
2 nếu mô-đun không thể được khởi tạo thỏa đáng. Hàm init phải trả lại đối tượng mô-đun cho người gọi nó, để sau đó nó được chèn vào
#define PY_SSIZE_T_CLEAN
#include 
55

Khi nhúng Python, hàm

#define PY_SSIZE_T_CLEAN
#include 
09 không được gọi tự động trừ khi có một mục nhập trong bảng
#define PY_SSIZE_T_CLEAN
#include 
57. Để thêm mô-đun vào bảng khởi tạo, hãy sử dụng , tùy chọn, sau đó nhập mô-đun

#define PY_SSIZE_T_CLEAN
#include 
3

Ghi chú

Việc xóa các mục nhập khỏi

#define PY_SSIZE_T_CLEAN
#include 
55 hoặc nhập các mô-đun đã biên dịch vào nhiều trình thông dịch trong một quy trình (hoặc theo sau một ____9_______60 mà không có một ________61 can thiệp) có thể gây ra sự cố cho một số mô-đun mở rộng. Tác giả mô-đun mở rộng nên thận trọng khi khởi tạo cấu trúc dữ liệu nội bộ

Một mô-đun ví dụ quan trọng hơn được bao gồm trong bản phân phối nguồn Python là

#define PY_SSIZE_T_CLEAN
#include 
62. Tệp này có thể được sử dụng làm mẫu hoặc chỉ đọc làm ví dụ

Ghi chú

Không giống như ví dụ

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
7 của chúng tôi,
#define PY_SSIZE_T_CLEAN
#include 
64 sử dụng khởi tạo nhiều giai đoạn (mới trong Python 3. 5), trong đó cấu trúc PyModuleDef được trả về từ
#define PY_SSIZE_T_CLEAN
#include 
65 và việc tạo mô-đun được để lại cho máy móc nhập khẩu. Để biết chi tiết về khởi tạo nhiều giai đoạn, xem PEP 489

1. 5. Biên dịch và liên kết

Có hai việc nữa cần làm trước khi bạn có thể sử dụng tiện ích mở rộng mới của mình. biên dịch và liên kết nó với hệ thống Python. Nếu bạn sử dụng tải động, các chi tiết có thể phụ thuộc vào kiểu tải động mà hệ thống của bạn sử dụng;

Nếu bạn không thể sử dụng tải động hoặc nếu bạn muốn biến mô-đun của mình thành một phần cố định của trình thông dịch Python, bạn sẽ phải thay đổi thiết lập cấu hình và xây dựng lại trình thông dịch. May mắn thay, điều này rất đơn giản trên Unix. chỉ cần đặt tệp của bạn (ví dụ: ____0_______9) vào thư mục

#define PY_SSIZE_T_CLEAN
#include 
67 của bản phân phối nguồn đã giải nén, thêm một dòng vào tệp
#define PY_SSIZE_T_CLEAN
#include 
68 mô tả tệp của bạn

#define PY_SSIZE_T_CLEAN
#include 
4

và xây dựng lại trình thông dịch bằng cách chạy make trong thư mục toplevel. Bạn cũng có thể chạy make trong thư mục con

#define PY_SSIZE_T_CLEAN
#include 
67, nhưng trước tiên bạn phải xây dựng lại
#define PY_SSIZE_T_CLEAN
#include 
70 ở đó bằng cách chạy 'make Makefile'. (Điều này là cần thiết mỗi khi bạn thay đổi tệp
#define PY_SSIZE_T_CLEAN
#include 
71. )

Ví dụ, nếu mô-đun của bạn yêu cầu các thư viện bổ sung để liên kết, thì các thư viện này cũng có thể được liệt kê trên dòng trong tệp cấu hình

#define PY_SSIZE_T_CLEAN
#include 
5

1. 6. Gọi hàm Python từ C

Cho đến nay, chúng tôi đã tập trung vào việc làm cho các hàm C có thể gọi được từ Python. Đảo ngược cũng hữu ích. gọi các hàm Python từ C. Điều này đặc biệt xảy ra đối với các thư viện hỗ trợ cái gọi là chức năng "gọi lại". Nếu một giao diện C sử dụng các cuộc gọi lại, Python tương đương thường cần cung cấp một cơ chế gọi lại cho lập trình viên Python; . Các công dụng khác cũng có thể tưởng tượng được

May mắn thay, trình thông dịch Python dễ dàng được gọi theo cách đệ quy và có một giao diện chuẩn để gọi một hàm Python. (Tôi sẽ không bàn sâu về cách gọi trình phân tích cú pháp Python với một chuỗi cụ thể làm đầu vào — nếu bạn quan tâm, hãy xem cách triển khai tùy chọn dòng lệnh trong

#define PY_SSIZE_T_CLEAN
#include 
73 từ mã nguồn Python. )

Gọi một hàm Python thật dễ dàng. Đầu tiên, chương trình Python bằng cách nào đó phải chuyển cho bạn đối tượng hàm Python. Bạn nên cung cấp một chức năng (hoặc một số giao diện khác) để thực hiện việc này. Khi hàm này được gọi, hãy lưu một con trỏ vào đối tượng hàm Python (hãy cẩn thận với nó. ) trong một biến toàn cục — hoặc bất cứ nơi nào bạn thấy phù hợp. Ví dụ: chức năng sau đây có thể là một phần của định nghĩa mô-đun

#define PY_SSIZE_T_CLEAN
#include 
6

Chức năng này phải được đăng ký với trình thông dịch bằng cờ; . Hàm và các đối số của nó được ghi lại trong phần

Các macro và tăng/giảm số lượng tham chiếu của một đối tượng và an toàn khi có mặt của con trỏ

sts = system(command);
2 (nhưng lưu ý rằng tạm thời sẽ không phải là ____27_______2 trong ngữ cảnh này). Thông tin thêm về chúng trong phần

Sau đó, khi đến lúc gọi hàm, bạn gọi hàm C. Hàm này có hai đối số, cả hai đều trỏ tới các đối tượng Python tùy ý. hàm Python và danh sách đối số. Danh sách đối số phải luôn là một đối tượng tuple, có độ dài bằng số lượng đối số. Để gọi hàm Python không có đối số, hãy chuyển vào

sts = system(command);
2 hoặc một bộ dữ liệu trống; . trả về một bộ khi chuỗi định dạng của nó bao gồm 0 hoặc nhiều mã định dạng giữa các dấu ngoặc đơn. Ví dụ

#define PY_SSIZE_T_CLEAN
#include 
7

trả về một con trỏ đối tượng Python. đây là giá trị trả về của hàm Python. là "tham chiếu-đếm-trung tính" đối với các đối số của nó. Trong ví dụ này, một bộ dữ liệu mới đã được tạo để phục vụ như danh sách đối số, được -ed ngay sau lệnh gọi

Giá trị trả về là “new”. nó là một đối tượng hoàn toàn mới hoặc nó là một đối tượng hiện có có số lượng tham chiếu đã được tăng lên. Vì vậy, trừ khi bạn muốn lưu nó trong một biến toàn cục, bằng cách nào đó bạn nên kết quả, thậm chí (đặc biệt là. ) nếu bạn không quan tâm đến giá trị của nó

Tuy nhiên, trước khi bạn làm điều này, điều quan trọng là phải kiểm tra xem giá trị trả về không phải là ____27_______2. Nếu đúng như vậy, hàm Python sẽ kết thúc bằng cách đưa ra một ngoại lệ. Nếu mã C được gọi được gọi từ Python, thì bây giờ nó sẽ trả về một dấu hiệu lỗi cho trình gọi Python của nó, vì vậy trình thông dịch có thể in dấu vết ngăn xếp hoặc mã Python đang gọi có thể xử lý ngoại lệ. Nếu điều này là không thể hoặc mong muốn, ngoại lệ sẽ được xóa bằng cách gọi. Ví dụ

#define PY_SSIZE_T_CLEAN
#include 
8

Tùy thuộc vào giao diện mong muốn của hàm gọi lại Python, bạn cũng có thể phải cung cấp một danh sách đối số cho. Trong một số trường hợp, danh sách đối số cũng được cung cấp bởi chương trình Python, thông qua cùng một giao diện đã chỉ định chức năng gọi lại. Sau đó, nó có thể được lưu và sử dụng theo cách tương tự như đối tượng chức năng. Trong các trường hợp khác, bạn có thể phải xây dựng một bộ dữ liệu mới để chuyển làm danh sách đối số. Cách đơn giản nhất để làm điều này là gọi. Ví dụ: nếu bạn muốn chuyển mã sự kiện tích hợp, bạn có thể sử dụng mã sau

#define PY_SSIZE_T_CLEAN
#include 
9

Lưu ý vị trí của

#define PY_SSIZE_T_CLEAN
#include 
95 ngay sau cuộc gọi, trước khi kiểm tra lỗi. Cũng lưu ý rằng nói đúng ra mã này chưa hoàn thành. có thể hết bộ nhớ và điều này cần được kiểm tra

Bạn cũng có thể gọi một hàm có đối số từ khóa bằng cách sử dụng , hỗ trợ đối số và đối số từ khóa. Như trong ví dụ trên, chúng ta sử dụng để xây dựng từ điển

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
0

1. 7. Trích xuất tham số trong Hàm mở rộng

Hàm được khai báo như sau

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
1

Đối số arg phải là một đối tượng tuple chứa danh sách đối số được truyền từ Python sang hàm C. Đối số định dạng phải là một chuỗi định dạng, có cú pháp được giải thích trong Hướng dẫn tham khảo API Python/C. Các đối số còn lại phải là địa chỉ của các biến có kiểu được xác định bởi chuỗi định dạng

Lưu ý rằng trong khi kiểm tra xem các đối số Python có các loại được yêu cầu hay không, nó không thể kiểm tra tính hợp lệ của địa chỉ của các biến C được chuyển đến lệnh gọi. nếu bạn mắc lỗi ở đó, mã của bạn có thể sẽ bị lỗi hoặc ít nhất là ghi đè lên các bit ngẫu nhiên trong bộ nhớ. Vì vậy hãy cẩn thận

Lưu ý rằng bất kỳ tham chiếu đối tượng Python nào được cung cấp cho người gọi đều là tham chiếu mượn;

Một số cuộc gọi ví dụ

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
2

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
3

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
4

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
5

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
6

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
7

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
8

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
9

1. 8. Thông số từ khóa cho chức năng mở rộng

Hàm được khai báo như sau

static PyObject *SpamError;
0

Các tham số đối số và định dạng giống hệt với các tham số của hàm. Tham số kwdict là từ điển các từ khóa nhận được dưới dạng tham số thứ ba từ thời gian chạy Python. Tham số kwlist là một danh sách các chuỗi kết thúc bằng

sts = system(command);
2 xác định các tham số; . Khi thành công, trả về true, nếu không, nó trả về false và đưa ra một ngoại lệ thích hợp

Ghi chú

Không thể phân tích cú pháp các bộ dữ liệu lồng nhau khi sử dụng đối số từ khóa. Các tham số từ khóa được truyền vào không có trong danh sách kwlist sẽ gây ra sự gia tăng

Đây là mô-đun mẫu sử dụng từ khóa, dựa trên ví dụ của Geoff Philbrick (philbrick @ hks . com).

static PyObject *SpamError;
1

1. 9. Xây dựng giá trị tùy ý

Chức năng này là đối trọng của. Nó được khai báo như sau

static PyObject *SpamError;
2

Nó nhận ra một tập hợp các đơn vị định dạng tương tự như các đơn vị được nhận dạng bởi , nhưng các đối số (là đầu vào của hàm, không phải đầu ra) không được là con trỏ, chỉ là giá trị. Nó trả về một đối tượng Python mới, phù hợp để trả về từ hàm C được gọi từ Python

Một sự khác biệt với. trong khi cái sau yêu cầu đối số đầu tiên của nó phải là một bộ (vì danh sách đối số Python luôn được biểu diễn dưới dạng các bộ bên trong), nên không phải lúc nào cũng xây dựng một bộ. Nó chỉ xây dựng một bộ nếu chuỗi định dạng của nó chứa hai hoặc nhiều đơn vị định dạng. Nếu chuỗi định dạng trống, nó sẽ trả về

#define PY_SSIZE_T_CLEAN
#include 
29; . Để buộc nó trả về một bộ có kích thước 0 hoặc một, hãy đặt dấu ngoặc đơn cho chuỗi định dạng

Các ví dụ (ở bên trái cuộc gọi, ở bên phải giá trị Python kết quả)

static PyObject *SpamError;
3

1. 10. Số lượng tham chiếu

Trong các ngôn ngữ như C hoặc C++, lập trình viên chịu trách nhiệm cấp phát động và hủy cấp phát bộ nhớ trên heap. Trong C, điều này được thực hiện bằng cách sử dụng các hàm

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 và
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6. Trong C++, các toán tử
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
13 và
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
14 được sử dụng với ý nghĩa cơ bản giống nhau và chúng tôi sẽ giới hạn phần thảo luận sau trong trường hợp C

Mỗi khối bộ nhớ được phân bổ với

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 cuối cùng sẽ được trả về nhóm bộ nhớ khả dụng bằng chính xác một lệnh gọi tới
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6. Điều quan trọng là phải gọi cho
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 vào đúng thời điểm. Nếu địa chỉ của một khối bị quên nhưng
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 không được gọi cho nó, bộ nhớ mà nó chiếm giữ không thể được sử dụng lại cho đến khi chương trình kết thúc. Điều này được gọi là rò rỉ bộ nhớ. Mặt khác, nếu một chương trình gọi
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 cho một khối và sau đó tiếp tục sử dụng khối đó, nó sẽ tạo ra xung đột với việc sử dụng lại khối thông qua một lệnh gọi
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 khác. Điều này được gọi là sử dụng bộ nhớ giải phóng. Nó có những hậu quả xấu tương tự như tham chiếu dữ liệu chưa được khởi tạo - kết xuất lõi, kết quả sai, sự cố bí ẩn

Nguyên nhân phổ biến của rò rỉ bộ nhớ là các đường dẫn bất thường thông qua mã. Chẳng hạn, một hàm có thể phân bổ một khối bộ nhớ, thực hiện một số phép tính và sau đó giải phóng lại khối đó. Bây giờ, một thay đổi trong các yêu cầu đối với hàm có thể thêm một phép thử vào phép tính để phát hiện tình trạng lỗi và có thể trả về sớm từ hàm. Rất dễ quên giải phóng khối bộ nhớ được phân bổ khi thực hiện thao tác thoát sớm này, đặc biệt là khi nó được thêm vào mã sau này. Những rò rỉ như vậy, một khi đã xuất hiện, thường không bị phát hiện trong một thời gian dài. thoát lỗi chỉ được thực hiện trong một phần nhỏ của tất cả các cuộc gọi và hầu hết các máy hiện đại đều có nhiều bộ nhớ ảo, do đó, rò rỉ chỉ trở nên rõ ràng trong một quy trình dài sử dụng chức năng rò rỉ thường xuyên. Do đó, điều quan trọng là phải ngăn chặn rò rỉ xảy ra bằng cách có một quy ước hoặc chiến lược viết mã để giảm thiểu loại lỗi này.

Vì Python sử dụng rất nhiều

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 và
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6, nên nó cần một chiến lược để tránh rò rỉ bộ nhớ cũng như sử dụng bộ nhớ được giải phóng. Phương pháp được chọn được gọi là đếm tham chiếu. Nguyên tắc là đơn giản. mọi đối tượng chứa một bộ đếm, được tăng lên khi một tham chiếu đến đối tượng được lưu trữ ở đâu đó và bị giảm đi khi một tham chiếu đến nó bị xóa. Khi bộ đếm về 0, tham chiếu cuối cùng đến đối tượng đã bị xóa và đối tượng được giải phóng

Một chiến lược thay thế được gọi là thu gom rác tự động. (Đôi khi, việc đếm tham chiếu còn được gọi là chiến lược thu gom rác, do đó tôi sử dụng "tự động" để phân biệt hai. ) Ưu điểm lớn của việc thu gom rác tự động là người dùng không cần gọi ____________6 một cách rõ ràng. (Một lợi thế khác được khẳng định là sự cải thiện về tốc độ hoặc mức sử dụng bộ nhớ — tuy nhiên đây không phải là sự thật khó hiểu. ) Nhược điểm là đối với C, không có bộ thu gom rác tự động di động thực sự, trong khi việc đếm tham chiếu có thể được thực hiện một cách di động (miễn là các chức năng

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 và
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 khả dụng — điều mà Tiêu chuẩn C đảm bảo). Có thể một ngày nào đó, một bộ thu gom rác tự động đủ di động sẽ có sẵn cho C. Cho đến lúc đó, chúng ta sẽ phải sống với số lượng tài liệu tham khảo

Mặc dù Python sử dụng triển khai đếm tham chiếu truyền thống, nhưng nó cũng cung cấp một trình phát hiện chu kỳ hoạt động để phát hiện các chu kỳ tham chiếu. Điều này cho phép các ứng dụng không phải lo lắng về việc tạo các tham chiếu vòng tròn trực tiếp hoặc gián tiếp; . Các chu trình tham chiếu bao gồm các đối tượng chứa các tham chiếu (có thể là gián tiếp) đến chính chúng, sao cho mỗi đối tượng trong chu trình có số lượng tham chiếu khác không. Các triển khai đếm tham chiếu điển hình không thể lấy lại bộ nhớ thuộc về bất kỳ đối tượng nào trong chu trình tham chiếu hoặc được tham chiếu từ các đối tượng trong chu trình, ngay cả khi không có thêm tham chiếu nào đến chính chu trình đó

Máy dò chu kỳ có thể phát hiện các chu kỳ rác và có thể thu hồi chúng. Mô-đun hiển thị cách chạy trình phát hiện (chức năng), cũng như các giao diện cấu hình và khả năng vô hiệu hóa trình phát hiện khi chạy

1. 10. 1. Đếm tham chiếu trong Python

Có hai macro,

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
28 và
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
29, xử lý việc tăng và giảm số lượng tham chiếu. cũng giải phóng đối tượng khi số đếm về 0. Để linh hoạt, nó không gọi trực tiếp
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
6 — thay vào đó, nó thực hiện cuộc gọi thông qua một con trỏ hàm trong đối tượng kiểu của đối tượng. Với mục đích này (và những mục đích khác), mọi đối tượng cũng chứa một con trỏ tới đối tượng kiểu của nó

Câu hỏi lớn bây giờ vẫn còn. khi nào thì sử dụng

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
28 và
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
29? . Không ai “sở hữu” một đồ vật; . Số lượng tham chiếu của một đối tượng hiện được xác định là số lượng tham chiếu thuộc sở hữu đối với nó. Chủ sở hữu của một tham chiếu có trách nhiệm gọi khi tham chiếu không còn cần thiết. Quyền sở hữu của một tham chiếu có thể được chuyển giao. Có ba cách để loại bỏ một tham chiếu được sở hữu. chuyển nó đi, lưu trữ nó, hoặc gọi. Quên loại bỏ một tài liệu tham khảo được sở hữu sẽ tạo ra rò rỉ bộ nhớ

Cũng có thể mượn một tham chiếu đến một đối tượng. Người mượn tài liệu tham khảo không nên gọi. Người mượn không được giữ đồ vật lâu hơn chủ sở hữu mà nó đã được mượn. Sử dụng tài liệu tham khảo mượn sau khi chủ sở hữu đã loại bỏ nó có nguy cơ sử dụng bộ nhớ được giải phóng và nên tránh hoàn toàn

Ưu điểm của việc mượn so với việc sở hữu một tham chiếu là bạn không cần quan tâm đến việc xử lý tham chiếu trên tất cả các đường dẫn có thể thông qua mã — nói cách khác, với một tham chiếu mượn, bạn không gặp rủi ro bị rò rỉ khi một . Nhược điểm của việc vay mượn so với sở hữu là có một số tình huống tế nhị trong đó mã có vẻ đúng, một tham chiếu mượn có thể được sử dụng sau khi chủ sở hữu mà nó được mượn trên thực tế đã xử lý nó.

Một tài liệu tham khảo mượn có thể được thay đổi thành một tài liệu tham khảo sở hữu bằng cách gọi. Điều này không ảnh hưởng đến trạng thái của chủ sở hữu mà từ đó tham chiếu được mượn — nó tạo ra một tham chiếu mới được sở hữu và trao toàn bộ trách nhiệm cho chủ sở hữu (chủ sở hữu mới phải hủy bỏ tham chiếu đúng cách, cũng như chủ sở hữu trước đó)

1. 10. 2. Quy tắc sở hữu

Bất cứ khi nào một tham chiếu đối tượng được chuyển vào hoặc ra khỏi một chức năng, nó là một phần của đặc tả giao diện của chức năng cho dù quyền sở hữu có được chuyển cùng với tham chiếu hay không

Hầu hết các hàm trả về một tham chiếu đến một đối tượng đều chuyển quyền sở hữu với tham chiếu đó. Cụ thể, tất cả các hàm có chức năng tạo đối tượng mới, chẳng hạn như và , chuyển quyền sở hữu cho người nhận. Ngay cả khi đối tượng không thực sự mới, bạn vẫn nhận quyền sở hữu một tham chiếu mới cho đối tượng đó. Chẳng hạn, duy trì bộ nhớ cache của các giá trị phổ biến và có thể trả về một tham chiếu đến một mục đã lưu trong bộ nhớ cache

Chẳng hạn, nhiều chức năng trích xuất các đối tượng từ các đối tượng khác cũng chuyển quyền sở hữu với tham chiếu. Tuy nhiên, bức tranh ở đây kém rõ ràng hơn vì một vài thói quen phổ biến là ngoại lệ. , , và tất cả các tham chiếu trả về mà bạn mượn từ bộ, danh sách hoặc từ điển

Hàm này cũng trả về một tham chiếu mượn, mặc dù nó có thể thực sự tạo ra đối tượng mà nó trả về. điều này là có thể bởi vì một tham chiếu sở hữu đến đối tượng được lưu trữ trong

#define PY_SSIZE_T_CLEAN
#include 
55

Khi bạn chuyển một tham chiếu đối tượng vào một hàm khác, nói chung, hàm đó sẽ mượn tham chiếu từ bạn — nếu nó cần lưu trữ nó, nó sẽ sử dụng để trở thành một chủ sở hữu độc lập. Có chính xác hai ngoại lệ quan trọng đối với quy tắc này. và. Các chức năng này tiếp nhận quyền sở hữu đối với mục được chuyển cho chúng — ngay cả khi chúng không thành công. (Lưu ý rằng và bạn bè không chiếm quyền sở hữu - họ là “bình thường. ”)

Khi một hàm C được gọi từ Python, nó sẽ mượn các tham chiếu đến các đối số của nó từ trình gọi. Người gọi sở hữu một tham chiếu đến đối tượng, vì vậy thời gian tồn tại của tham chiếu mượn được đảm bảo cho đến khi hàm trả về. Chỉ khi một tham chiếu mượn như vậy phải được lưu trữ hoặc chuyển đi, nó phải được biến thành một tham chiếu được sở hữu bằng cách gọi

Tham chiếu đối tượng được trả về từ hàm C được gọi từ Python phải là tham chiếu được sở hữu — quyền sở hữu được chuyển từ hàm sang trình gọi của nó

1. 10. 3. Băng mỏng

Có một vài tình huống mà việc sử dụng tài liệu tham khảo mượn dường như vô hại có thể dẫn đến các vấn đề. Tất cả những điều này đều liên quan đến các lời gọi ngầm định của trình thông dịch, điều này có thể khiến chủ sở hữu của một tham chiếu loại bỏ nó

Trường hợp đầu tiên và quan trọng nhất cần biết là sử dụng trên một đối tượng không liên quan trong khi mượn tham chiếu đến một mục danh sách. Ví dụ

static PyObject *SpamError;
4

Đầu tiên, hàm này mượn một tham chiếu đến

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
54, sau đó thay thế
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
55 bằng giá trị
#define PY_SSIZE_T_CLEAN
#include 
38 và cuối cùng in ra tham chiếu đã mượn. Trông vô hại, phải không?

Hãy theo luồng điều khiển vào. Danh sách sở hữu các tham chiếu đến tất cả các mục của nó, vì vậy khi mục 1 được thay thế, nó phải loại bỏ mục 1 ban đầu. Bây giờ, hãy giả sử rằng mục ban đầu 1 là một thể hiện của một lớp do người dùng định nghĩa và giả sử thêm rằng lớp đó đã định nghĩa một phương thức

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
58. Nếu thể hiện của lớp này có số lượng tham chiếu là 1, việc loại bỏ nó sẽ gọi phương thức
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
58 của nó

Vì nó được viết bằng Python nên phương thức

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
58 có thể thực thi mã Python tùy ý. Nó có thể làm điều gì đó để vô hiệu hóa tham chiếu đến
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
61 trong
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
62 không? . Giả sử rằng danh sách được chuyển vào
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
62 có thể truy cập được bằng phương thức
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
58, nó có thể thực thi một câu lệnh có hiệu lực của
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
65 và giả sử đây là tham chiếu cuối cùng đến đối tượng đó, nó sẽ giải phóng bộ nhớ được liên kết với nó, do đó làm mất hiệu lực của
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
61

Giải pháp, một khi bạn biết nguồn gốc của vấn đề, thật dễ dàng. tạm thời tăng số lượng tham chiếu. Phiên bản chính xác của chức năng đọc

static PyObject *SpamError;
5

Đây là một câu chuyện có thật. Một phiên bản cũ hơn của Python chứa các biến thể của lỗi này và ai đó đã dành một khoảng thời gian đáng kể trong trình gỡ lỗi C để tìm ra lý do tại sao các phương thức

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
58 của anh ấy lại thất bại…

Trường hợp thứ hai của các vấn đề với một tài liệu tham khảo mượn là một biến thể liên quan đến chủ đề. Thông thường, nhiều luồng trong trình thông dịch Python không thể cản trở nhau, bởi vì có một khóa toàn cầu bảo vệ toàn bộ không gian đối tượng của Python. Tuy nhiên, có thể tạm thời giải phóng khóa này bằng cách sử dụng macro và lấy lại bằng cách sử dụng. Điều này phổ biến xung quanh việc chặn các cuộc gọi I/O, để cho phép các luồng khác sử dụng bộ xử lý trong khi chờ I/O hoàn tất. Rõ ràng, hàm sau có cùng vấn đề với hàm trước

static PyObject *SpamError;
6

1. 10. 4. Con trỏ NULL

Nói chung, các hàm lấy tham chiếu đối tượng làm đối số không yêu cầu bạn chuyển các con trỏ

sts = system(command);
2 cho chúng và sẽ kết xuất lõi (hoặc gây ra kết xuất lõi sau này) nếu bạn làm như vậy. Các hàm trả về tham chiếu đối tượng thường chỉ trả về
sts = system(command);
2 để cho biết đã xảy ra ngoại lệ. Lý do không kiểm tra các đối số
sts = system(command);
2 là các hàm thường chuyển các đối tượng mà chúng nhận được sang hàm khác — nếu mỗi hàm được kiểm tra cho
sts = system(command);
2, thì sẽ có rất nhiều kiểm tra dư thừa và mã sẽ chạy chậm hơn

Tốt hơn là chỉ kiểm tra

sts = system(command);
2 tại “nguồn. ” khi một con trỏ có thể là
sts = system(command);
2 được nhận, ví dụ, từ
if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
5 hoặc từ một hàm có thể đưa ra một ngoại lệ

Các macro và không kiểm tra các con trỏ

sts = system(command);
2 — tuy nhiên, các biến thể của chúng và không

Các macro để kiểm tra một loại đối tượng cụ thể (_______25_______82) không kiểm tra các con trỏ ____27_______2 — một lần nữa, có nhiều mã gọi một vài trong số này liên tiếp để kiểm tra một đối tượng theo nhiều loại dự kiến ​​khác nhau và điều này sẽ tạo ra các kiểm tra dư thừa. Không có biến thể nào với việc kiểm tra

sts = system(command);
2

Cơ chế gọi hàm C đảm bảo rằng danh sách đối số được truyền cho các hàm C (

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
85 trong các ví dụ) không bao giờ là
sts = system(command);
2 — thực tế nó đảm bảo rằng nó luôn là một bộ

Đó là một lỗi nghiêm trọng khi để một con trỏ

sts = system(command);
2 “thoát” cho người dùng Python

1. 11. Viết phần mở rộng trong C++

Có thể viết các module mở rộng trong C++. Một số hạn chế áp dụng. Nếu chương trình chính (trình thông dịch Python) được biên dịch và liên kết bởi trình biên dịch C, thì không thể sử dụng các đối tượng toàn cục hoặc tĩnh với các hàm tạo. Đây không phải là vấn đề nếu chương trình chính được liên kết bởi trình biên dịch C++. Các hàm sẽ được gọi bởi trình thông dịch Python (cụ thể là các hàm khởi tạo mô-đun) phải được khai báo bằng cách sử dụng

#define PY_SSIZE_T_CLEAN
#include 
48. Không cần thiết phải đính kèm các tệp tiêu đề Python trong
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
89 — chúng đã sử dụng biểu mẫu này nếu ký hiệu
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
90 được xác định (tất cả các trình biên dịch C++ gần đây đều xác định ký hiệu này)

1. 12. Cung cấp API C cho Mô-đun mở rộng

Nhiều mô-đun mở rộng chỉ cung cấp các chức năng và loại mới được sử dụng từ Python, nhưng đôi khi mã trong mô-đun mở rộng có thể hữu ích cho các mô-đun mở rộng khác. Ví dụ: một mô-đun mở rộng có thể triển khai một loại "bộ sưu tập" hoạt động giống như danh sách không có thứ tự. Giống như loại danh sách Python tiêu chuẩn có API C cho phép các mô-đun mở rộng tạo và thao tác với danh sách, loại bộ sưu tập mới này phải có một bộ hàm C để thao tác trực tiếp từ các mô-đun mở rộng khác

Thoạt nhìn điều này có vẻ dễ dàng. chỉ cần viết các hàm (tất nhiên là không khai báo chúng

#define PY_SSIZE_T_CLEAN
#include 
46), cung cấp tệp tiêu đề thích hợp và ghi lại C API. Và trên thực tế, điều này sẽ hoạt động nếu tất cả các mô-đun mở rộng luôn được liên kết tĩnh với trình thông dịch Python. Tuy nhiên, khi các mô-đun được sử dụng làm thư viện dùng chung, các ký hiệu được xác định trong một mô-đun có thể không hiển thị với mô-đun khác. Các chi tiết về khả năng hiển thị phụ thuộc vào hệ điều hành; . Và ngay cả khi các biểu tượng hiển thị trên toàn cầu, mô-đun có các chức năng mà người ta muốn gọi có thể chưa được tải

Do đó, tính di động yêu cầu không đưa ra bất kỳ giả định nào về khả năng hiển thị biểu tượng. Điều này có nghĩa là tất cả các ký hiệu trong mô-đun mở rộng phải được khai báo

#define PY_SSIZE_T_CLEAN
#include 
46, ngoại trừ chức năng khởi tạo của mô-đun, để tránh xung đột tên với các mô-đun mở rộng khác (như đã thảo luận trong phần ). Và điều đó có nghĩa là các biểu tượng có thể truy cập được từ các mô-đun mở rộng khác phải được xuất theo một cách khác

Python cung cấp một cơ chế đặc biệt để truyền thông tin mức C (con trỏ) từ mô-đun mở rộng này sang mô-đun mở rộng khác. viên nang. Capsule là một kiểu dữ liệu Python lưu trữ một con trỏ ( void* ). Các viên nang chỉ có thể được tạo và truy cập thông qua API C của chúng, nhưng chúng có thể được chuyển qua lại giống như bất kỳ đối tượng Python nào khác. Đặc biệt, chúng có thể được gán cho một tên trong không gian tên của mô-đun mở rộng. Sau đó, các mô-đun mở rộng khác có thể nhập mô-đun này, truy xuất giá trị của tên này và sau đó truy xuất con trỏ từ Capsule.

Có nhiều cách mà Viên nang có thể được sử dụng để xuất API C của mô-đun mở rộng. Mỗi chức năng có thể có Viên nang riêng hoặc tất cả các con trỏ API C có thể được lưu trữ trong một mảng có địa chỉ được xuất bản trong Viên nang. Và các tác vụ lưu trữ và truy xuất con trỏ khác nhau có thể được phân phối theo những cách khác nhau giữa mô-đun cung cấp mã và mô-đun máy khách

Cho dù bạn chọn phương pháp nào, điều quan trọng là bạn phải đặt tên đúng cho Viên nang của mình. Hàm nhận tham số tên ( const char* ); . Viên nang được đặt tên phù hợp cung cấp một mức độ an toàn cho loại thời gian chạy; .

Cụ thể, các Viên nang được sử dụng để hiển thị API C phải được đặt tên theo quy ước này

static PyObject *SpamError;
7

Chức năng tiện lợi giúp dễ dàng tải API C được cung cấp qua Capsule, nhưng chỉ khi tên Capsule phù hợp với quy ước này. Hành vi này mang lại cho người dùng API C mức độ chắc chắn cao rằng Viên nang họ tải chứa API C chính xác

Ví dụ sau minh họa một cách tiếp cận đặt phần lớn gánh nặng lên người viết mô-đun xuất, phù hợp với các mô-đun thư viện thường được sử dụng. Nó lưu trữ tất cả các con trỏ API C (chỉ một con trỏ trong ví dụ. ) trong một mảng void con trỏ trở thành giá trị của Capsule. Tệp tiêu đề tương ứng với mô-đun cung cấp một macro đảm nhiệm việc nhập mô-đun và truy xuất các con trỏ API C của nó; .

Mô-đun xuất khẩu là một sửa đổi của mô-đun

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
7 từ phần. Hàm
#define PY_SSIZE_T_CLEAN
#include 
26 không gọi trực tiếp hàm thư viện C
PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
8, mà là hàm
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
99, tất nhiên hàm này sẽ thực hiện điều gì đó phức tạp hơn trong thực tế (chẳng hạn như thêm “thư rác” vào mọi lệnh). Chức năng này
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
99 cũng được xuất sang các mô-đun mở rộng khác

Hàm

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
99 là một hàm C đơn giản, được khai báo
#define PY_SSIZE_T_CLEAN
#include 
46 như mọi thứ khác

static PyObject *SpamError;
8

Chức năng

#define PY_SSIZE_T_CLEAN
#include 
34 được sửa đổi một cách tầm thường

static PyObject *SpamError;
9

Ở phần đầu của mô-đun, ngay sau dòng

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
0

phải thêm hai dòng nữa

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
1

static PyObject *SpamError;
04 được sử dụng để thông báo cho tệp tiêu đề rằng nó đang được đưa vào mô-đun xuất, không phải mô-đun máy khách. Cuối cùng, chức năng khởi tạo của mô-đun phải đảm nhiệm việc khởi tạo mảng con trỏ C API

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
2

Lưu ý rằng

static PyObject *SpamError;
05 được khai báo là
#define PY_SSIZE_T_CLEAN
#include 
46;

Phần lớn công việc nằm trong tệp tiêu đề

static PyObject *SpamError;
08, trông như thế này

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
3

Tất cả những gì mô-đun máy khách phải làm để có quyền truy cập vào hàm

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}
99 là gọi hàm (hay đúng hơn là macro)
static PyObject *SpamError;
10 trong hàm khởi tạo của nó

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
4

Nhược điểm chính của phương pháp này là tệp

static PyObject *SpamError;
08 khá phức tạp. Tuy nhiên, cấu trúc cơ bản là giống nhau đối với mỗi hàm được xuất nên chỉ phải học một lần

Cuối cùng, cần đề cập rằng Viên nang cung cấp chức năng bổ sung, đặc biệt hữu ích cho việc cấp phát bộ nhớ và hủy cấp phát con trỏ được lưu trữ trong Viên nang. Các chi tiết được mô tả trong Hướng dẫn tham khảo API Python/C trong phần và trong quá trình triển khai Viên nang (tệp

static PyObject *SpamError;
12 và
static PyObject *SpamError;
13 trong bản phân phối mã nguồn Python)

chú thích

Giao diện cho chức năng này đã tồn tại trong mô-đun tiêu chuẩn — nó được chọn làm ví dụ đơn giản và dễ hiểu

Ẩn dụ “mượn” tài liệu tham khảo không hoàn toàn đúng. chủ sở hữu vẫn có một bản sao của tài liệu tham khảo

Kiểm tra xem số lượng tham chiếu ít nhất là 1 không hoạt động — bản thân số lượng tham chiếu có thể nằm trong bộ nhớ được giải phóng và do đó có thể được sử dụng lại cho một đối tượng khác

Những đảm bảo này không giữ được khi bạn sử dụng quy ước gọi kiểu "cũ" - quy ước này vẫn được tìm thấy trong nhiều mã hiện có

FFI Python là gì?

Giao diện chức năng nước ngoài (FFI) là cơ chế mà chương trình được viết bằng một ngôn ngữ lập trình có thể gọi các quy trình hoặc sử dụng các dịch vụ được viết bằng ngôn ngữ lập trình khác.

Việc sử dụng CFFI trong Python là gì?

CFFI là gói bên ngoài cung cấp Giao diện hàm ngoại C cho Python. CFFI cho phép một người tương tác với hầu hết mọi mã C từ Python . Tuy nhiên, C++ hiện không được hỗ trợ.