Python – Lập trình mở rộng với C

0
17

Bất kỳ mã nào bạn viết bằng bất kỳ ngôn ngữ biên dịch nào như C, C ++ hoặc Java đều có thể được tích hợp hoặc nhập vào một tập lệnh Python khác. Mã này được coi là “phần mở rộng”.

Một mô-đun mở rộng Python không hơn gì một thư viện C bình thường. Trên máy Unix, các thư viện này thường kết thúc bằng .so (dành cho đối tượng được chia sẻ). Trên máy Windows, bạn thường thấy .dll (cho thư viện được liên kết động).

Điều kiện tiên quyết để viết tiện ích mở rộng

Để bắt đầu viết tiện ích mở rộng của bạn, bạn sẽ cần các tệp tiêu đề Python.

  • Trên máy Unix, điều này thường yêu cầu cài đặt gói dành riêng cho nhà phát triển như python2.5-dev .
  • Người dùng Windows nhận được các tiêu đề này như một phần của gói khi họ sử dụng trình cài đặt Python nhị phân.

Ngoài ra, giả sử rằng bạn có kiến ​​thức tốt về C hoặc C ++ để viết bất kỳ Phần mở rộng Python nào bằng lập trình C.

Đầu tiên hãy xem một Tiện ích mở rộng Python

Để có cái nhìn đầu tiên về mô-đun mở rộng Python, bạn cần phải nhóm mã của mình thành bốn phần:

  • Tệp tiêu đề Python.h .
  • Các chức năng C bạn muốn hiển thị dưới dạng giao diện từ mô-đun của bạn.
  • Một bảng ánh xạ tên các hàm của bạn khi các nhà phát triển Python nhìn thấy chúng thành các hàm C bên trong mô-đun mở rộng.
  • Một chức năng khởi tạo.

Tệp tiêu đề Python.h

Bạn cần bao gồm tệp tiêu đề Python.h trong tệp nguồn C của mình, tệp này cung cấp cho bạn quyền truy cập vào API Python nội bộ được sử dụng để kết nối mô-đun của bạn với trình thông dịch.

Bạn cần bao gồm tệp tiêu đề Python.h trong tệp nguồn C của mình, tệp này cung cấp cho bạn quyền truy cập vào API Python nội bộ được sử dụng để kết nối mô-đun của bạn với trình thông dịch.

Đảm bảo bao gồm Python.h trước bất kỳ tiêu đề nào khác mà bạn có thể cần. Bạn cần tuân theo các hàm mà bạn muốn gọi từ Python.

Các hàm C

Chữ ký của việc triển khai C các chức năng của bạn luôn có một trong ba dạng sau:

static PyObject *MyFunction( PyObject *self, PyObject *args ); 
static PyObject *MyFunctionWithKeywords(PyObject *self,
PyObject *args,
PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );

Mỗi một trong các khai báo trước trả về một đối tượng Python. Không có cái gọi là hàm void trong Python như trong C. Nếu bạn không muốn các hàm của mình trả về giá trị, hãy trả về giá trị C tương đương với giá trị None của Python . Các tiêu đề Python xác định một macro, Py_RETURN_NONE, thực hiện điều này cho chúng ta.

Tên của các hàm C của bạn có thể là bất cứ thứ gì bạn thích vì chúng không bao giờ được nhìn thấy bên ngoài mô-đun mở rộng. Chúng được định nghĩa là hàm tĩnh .

Các hàm C của bạn thường được đặt tên bằng cách kết hợp mô-đun Python và tên hàm với nhau, như được hiển thị ở đây:

static PyObject *module_func(PyObject *self, PyObject *args) { 
/* Do your stuff here. */
Py_RETURN_NONE;
}

Đây là một hàm Python được gọi là func bên trong mô -đun mô-đun . Bạn sẽ đưa các con trỏ đến các hàm C của mình vào bảng phương pháp cho mô-đun thường xuất hiện tiếp theo trong mã nguồn của bạn.

Bảng ánh xạ phương pháp

Bảng phương thức này là một mảng cấu trúc PyMethodDef đơn giản. Cấu trúc đó trông giống như thế này –

struct PyMethodDef { 
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc; };

Đây là mô tả về các thành viên của cấu trúc này –

  • ml_name – Đây là tên của hàm như trình thông dịch Python trình bày khi nó được sử dụng trong các chương trình Python.
  • ml_meth – Đây phải là địa chỉ của một hàm có bất kỳ chữ ký nào được mô tả trong phần trước.
  • ml_flags – Điều này cho trình thông dịch biết mà ml_meth đang sử dụng chữ ký nào trong ba chữ ký.
    • Cờ này thường có giá trị là METH_VARARGS.
    • Cờ này có thể được đặt theo từng bit HOẶC bằng METH_KEYWORDS nếu bạn muốn cho phép các đối số từ khóa vào hàm của mình.
    • Điều này cũng có thể có giá trị METH_NOARGS cho biết bạn không muốn chấp nhận bất kỳ đối số nào.
  • ml_doc – Đây là chuỗi doc cho hàm, có thể là NULL nếu bạn không muốn viết hàm này.

Bảng này cần được kết thúc bằng một sentinel bao gồm các giá trị NULL và 0 cho các thành viên thích hợp.

Thí dụ

Đối với hàm được xác định ở trên, chúng ta có bảng ánh xạ phương thức sau:

static PyMethodDef module_methods[] = { 
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};

Hàm khởi tạo

Phần cuối cùng của mô-đun mở rộng của bạn là chức năng khởi tạo. Hàm này được gọi bởi trình thông dịch Python khi mô-đun được tải. Yêu cầu hàm được đặt tên là init Module , trong đó Module là tên của module.

Hàm khởi tạo cần được xuất từ ​​thư viện mà bạn sẽ xây dựng. Các tiêu đề Python xác định PyMODINIT_FUNC để bao gồm các câu chú thích hợp để điều đó xảy ra cho môi trường cụ thể mà chúng tôi đang biên dịch. Tất cả những gì bạn phải làm là sử dụng nó khi xác định hàm.

Hàm khởi tạo C của bạn thường có cấu trúc tổng thể sau:

PyMODINIT_FUNC initModule() { 
Py_InitModule3(func, module_methods, "docstring...");
}

Đây là mô tả của hàm Py_InitModule3

  • func – Đây là chức năng được xuất.
  • module _methods – Đây là tên bảng ánh xạ được định nghĩa ở trên.
  • docstring – Đây là nhận xét bạn muốn đưa ra trong tiện ích mở rộng của mình.

Đặt tất cả những thứ này lại với nhau trông giống như sau:

#include <Python.h> 
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}

Thí dụ

Một ví dụ đơn giản sử dụng tất cả các khái niệm trên –

#include <Python.h>
static PyObject* helloworld(PyObject* self) {
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] =
"helloworld( ): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld,
METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void) {
Py_InitModule3("helloworld", helloworld_funcs,
"Extension module example!");
}

Ở đây, hàm Py_BuildValue được sử dụng để xây dựng một giá trị Python. Lưu mã trên trong tệp hello.c. Chúng ta sẽ xem cách biên dịch và cài đặt mô-đun này để được gọi từ tập lệnh Python.

Xây dựng và Cài đặt Tiện ích mở rộng

Các distutils gói làm cho nó rất dễ dàng để phân phối module Python, cả hai tinh khiết Python và mở rộng mô-đun, trong một cách tiêu chuẩn. Các mô-đun được phân phối ở dạng nguồn và được xây dựng và cài đặt thông qua một tập lệnh thiết lập thường được gọi là setup.py như sau.

Đối với mô-đun trên, bạn cần chuẩn bị tập lệnh setup.py sau –

from distutils.core import setup, Extension setup(name='helloworld', version='1.0',  \   
ext_modules=[Extension('helloworld', ['hello.c'])])

Bây giờ, hãy sử dụng lệnh sau, lệnh này sẽ thực hiện tất cả các bước biên dịch và liên kết cần thiết, với các lệnh và cờ của trình biên dịch và trình liên kết phù hợp, và sao chép thư viện động kết quả vào một thư mục thích hợp –

$ python setup.py install

Trên các hệ thống dựa trên Unix, rất có thể bạn sẽ cần chạy lệnh này dưới dạng root để có quyền ghi vào thư mục gói trang. Đây thường không phải là vấn đề trên Windows.

Nhập tiện ích mở rộng

Sau khi cài đặt tiện ích mở rộng của mình, bạn sẽ có thể nhập và gọi tiện ích mở rộng đó trong tập lệnh Python của mình như sau:

#!/usr/bin/python
import helloworld
print helloworld.helloworld()

Điều này sẽ tạo ra kết quả sau:

Hello, Python extensions!!

Thông số chức năng truyền

Vì rất có thể bạn sẽ muốn xác định các hàm chấp nhận đối số, bạn có thể sử dụng một trong các chữ ký khác cho các hàm C của mình. Ví dụ: hàm sau, chấp nhận một số tham số, sẽ được định nghĩa như thế này:

static PyObject *module_func(PyObject *self, PyObject *args) { 
/* Parse args and do something interesting here. */
Py_RETURN_NONE;
}

Bảng phương thức chứa một mục nhập cho hàm mới sẽ trông như thế này:

static PyMethodDef module_methods[] = { 
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};

Bạn có thể sử dụng hàm API PyArg_ParseTuple để trích xuất các đối số từ một con trỏ PyObject được truyền vào hàm C của bạn.

Đối số đầu tiên của PyArg_ParseTuple là đối số args. Đây là đối tượng bạn sẽ phân tích cú pháp . Đối số thứ hai là một chuỗi định dạng mô tả các đối số như bạn mong đợi chúng xuất hiện. Mỗi đối số được biểu diễn bằng một hoặc nhiều ký tự trong chuỗi định dạng như sau.

static PyObject *module_func(PyObject *self, PyObject *args) { 
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Do something interesting here. */
Py_RETURN_NONE;
}

Biên dịch phiên bản mới của mô-đun của bạn và nhập nó cho phép bạn gọi hàm mới với bất kỳ số lượng đối số nào thuộc bất kỳ loại nào –

module.func(1, s="three", d=2.0) 
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

Bạn có thể nghĩ ra nhiều biến thể hơn nữa.

Các PyArg_ParseTuple Chức năng

Đây là chữ ký chuẩn cho hàm PyArg_ParseTuple

int PyArg_ParseTuple(PyObject* tuple,char* format,...)

Hàm này trả về 0 cho các lỗi và một giá trị không bằng 0 cho thành công. tuple là PyObject * là đối số thứ hai của hàm C. Ở đây định dạng là một chuỗi C mô tả các đối số bắt buộc và tùy chọn.

Đây là danh sách các mã định dạng cho hàm PyArg_ParseTuple

Loại CÝ nghĩa
cchar Một chuỗi Python có độ dài 1 trở thành C char.
dgấp đôiMột float Python trở thành một C double.
fPhao nổiMột float Python trở thành một float C.
TôiintMột int Python trở thành một int C.
lDàiMột int Python trở thành một C dài.
Ldài dàiMột int Python trở thành một C dài
OPyObject *Nhận tham chiếu được mượn không phải NULL cho đối số Python.
Schar *Chuỗi Python không có null được nhúng vào C char *.
S#char * + intBất kỳ chuỗi Python nào đến địa chỉ và độ dài C.
t #char * + intBộ đệm một đoạn chỉ đọc tới địa chỉ và độ dài C.
uPy_UNICODE *Python Unicode không có null được nhúng vào C.
u #Py_UNICODE * + intBất kỳ địa chỉ và độ dài Python Unicode C nào.
w #char * + intĐọc / ghi bộ đệm một đoạn tới địa chỉ và độ dài C.
zchar *Giống như s, cũng chấp nhận None (đặt C char * thành NULL).
z #char * + intGiống như s #, cũng chấp nhận None (đặt C char * thành NULL).
(…)theo …Một chuỗi Python được coi là một đối số cho mỗi mục.
|Các đối số sau đây là tùy chọn.
:Kết thúc định dạng, theo sau là tên hàm cho các thông báo lỗi.
;Định dạng kết thúc, theo sau là toàn bộ văn bản thông báo lỗi.

Trả lại giá trị

Py_BuildValue nhận trong một chuỗi định dạng giống như PyArg_ParseTuple . Thay vì chuyển địa chỉ của các giá trị bạn đang xây dựng, bạn chuyển các giá trị thực tế vào. Đây là một ví dụ cho thấy cách triển khai một hàm thêm –

static PyObject *foo_add(PyObject *self, PyObject *args) { 
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}

Đây là những gì nó sẽ trông như thế nếu được triển khai bằng Python –

def add(a, b):
return (a + b)

Bạn có thể trả về hai giá trị từ hàm của mình như sau, giá trị này sẽ được lưu trữ bằng cách sử dụng một danh sách trong Python.

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) { 
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}

Đây là những gì nó sẽ trông như thế nếu được triển khai bằng Python –

def add_subtract(a, b): 
return (a + b, a - b)

Các Py_BuildValue Chức năng

Đây là chữ ký chuẩn cho hàm Py_BuildValue

PyObject* Py_BuildValue(char* format,...)

Ở đây định dạng là một chuỗi C mô tả đối tượng Python để xây dựng. Các đối số sau đây của Py_BuildValue là giá trị C mà từ đó kết quả được tạo. Các PyObject * Kết quả là một tham chiếu mới.

Bảng sau liệt kê các chuỗi mã thường được sử dụng, trong đó không hoặc nhiều hơn được nối vào định dạng chuỗi.

Loại CÝ nghĩa
ccharAC char trở thành một chuỗi Python có độ dài 1.
dgấp đôiAC double trở thành một float trong Python.
fPhao nổiAC float trở thành một float trong Python.
TôiintAC int trở thành một int Python.
lDàiAC long trở thành một int Python.
NPyObject *Truyền một đối tượng Python và đánh cắp một tham chiếu.
OPyObject *Truyền một đối tượng Python và TĂNG nó như bình thường.
O &chuyển đổi + void *Chuyển đổi tùy ý
Schar *C 0 kết thúc char * thành chuỗi Python hoặc NULL thành Không.
S#char * + intKý tự C * và độ dài thành chuỗi Python hoặc NULL thành Không.
uPy_UNICODE *C-wide, chuỗi kết thúc bằng null thành Python Unicode hoặc NULL thành Không.
u #Py_UNICODE * + intChuỗi rộng C và độ dài thành Python Unicode hoặc NULL thành Không.
w #char * + intĐọc / ghi bộ đệm một đoạn tới địa chỉ và độ dài C.
zchar *Giống như s, cũng chấp nhận None (đặt C char * thành NULL).
z #char * + intGiống như s #, cũng chấp nhận None (đặt C char * thành NULL).
(…)theo …Tạo bộ tuple Python từ các giá trị C.
[…]theo …Tạo danh sách Python từ các giá trị C.
{…}theo …Xây dựng từ điển Python từ các giá trị C, các khóa và giá trị xen kẽ.

Mã {…} xây dựng từ điển từ một số giá trị C chẵn, xen kẽ các khóa và giá trị. Ví dụ: Py_BuildValue (“{Issi}”, 23, “zig”, “zag”, 42) trả về một từ điển như {23: ‘zig’, ‘zag’: 42} của Python.

Khóa học PYTHON (IPD2020) cho người mới bắt đầu

LEAVE A REPLY

Please enter your comment!
Please enter your name here