Cộng đồng chia sẻ tri thức Lib24.vn

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

Gửi bởi: Phạm Thọ Thái Dương 20 tháng 2 2020 lúc 11:03:33


Mục lục
* * * * *

Bất kỳ mã nào bạn viết bằng bất kỳ ngôn ngữ được 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à một "phần mở rộng."

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

Yêu cầu trước cho phần mở rộng bằng văn bản

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

  1. Trên các 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, chẳng hạn như python2.5-dev .
  2. Người dùng Windows nhận 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ả định rằng bạn có kiến ​​thức tốt về C hoặc C ++ để viết bất kỳ Tiện ích mở rộng Python nào bằng lập trình C.

Trước tiên hãy xem 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 nhóm mã của mình thành bốn phần -

  1. Tệp tiêu đề Python.h .
  2. Các hàm C bạn muốn hiển thị dưới dạng giao diện từ mô-đun của mình.
  3. 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.
  4. 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, cho phép bạn truy cập vào API Python nội bộ được sử dụng để nối mô-đun của bạn vào 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 bao gồm các hàm bạn muốn gọi từ Python.

Hàm C

Chữ ký thực hiện C của các chức năng của bạn luôn có một trong ba hình thức 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 khai báo trước trả về một đối tượng Python. Không có thứ 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ề một giá trị, hãy trả về giá trị C tương đương với giá trị Không có 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à các 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ẽ đặt các con trỏ tới các hàm C của bạn vào bảng phương thức 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 thức

Bảng phương thức này là một mảng đơn giản của các cấu trúc PyMethodDef. 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;
};

Dưới đây là mô tả của các thành viên của cấu trúc này -

  1. 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.
  2. 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 lần giữ trước đó.
  3. ml_flags - Điều này cho người thông dịch biết ba chữ ký mà ml_meth đang sử dụng.Cờ này thường có giá trị METH_VARARGS.Cờ này có thể được bit OR ORed với 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.
  4. Cờ này thường có giá trị METH_VARARGS.
  5. Cờ này có thể được bit OR ORed với 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.
  6. Đ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.
  7. ml_doc - Đây là chuỗi doc cho hàm, có thể là NULL nếu bạn không cảm thấy muốn viết.

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 chức năng được đặt tên là init Module , trong đó Module là tên của mô-đun.

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 thần chú thích hợp cho đ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ả bạn phải làm là sử dụng nó khi xác định chức năng.

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

Dưới đây là mô tả về hàm Py_InitModule3 -

  1. func - Đây là chức năng được xuất.
  2. module _methods - Đây là tên bảng ánh xạ được xác định ở trên.
  3. doc Chuỗi - Đây là nhận xét bạn muốn đưa ra trong phần 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 giá trị Python. Lưu mã ở trên trong tập tin hello.c. Chúng ta sẽ thấy 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ờ, sử dụng lệnh sau, 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ờ 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 với quyền root để có quyền ghi vào thư mục gói trang. Điều này thường không phải là một vấn đề trên Windows.

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

Khi bạn đã 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!!

Truyền tham số chức năng

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_PudeTuple để 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 đối với PyArg_PudeTuple 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ố mà bạn mong đợi chúng xuất hiện. Mỗi đối số được đại diện bởi 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ố 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ể có thể đến với các biến thể thậm chí nhiều hơn.

Các PyArg_ParseTuple Chức năng

Dưới đây là chữ ký tiêu chuẩn cho chức năng PyArg_PudeTuple -

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

Hàm này trả về 0 cho các lỗi và 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.

Dưới đây là danh sách các mã định dạng cho hàm PyArg_PudeTuple -

Trả lại giá trị

Py_BuildValue có một chuỗi định dạng giống như PyArg_PudeTuple . Thay vì chuyển vào địa chỉ của các giá trị bạn đang xây dựng, bạn chuyển vào các giá trị thực tế. Dưới đây là một ví dụ cho thấy cách triển khai chức năng 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à giao diện của nó nếu được triển khai trong 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, điều này sẽ bị xóa bằng cách sử dụng 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à giao diện của nó nếu được triển khai trong Python -

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

Các Py_BuildValue Chức năng

Đây là chữ ký tiêu chuẩn cho chức năng 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à các giá trị C 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.

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


Được cập nhật: hôm qua lúc 8:23:21 | Lượt xem: 715