Python - การเขียนโปรแกรมส่วนขยายด้วย C
โค้ดใด ๆ ที่คุณเขียนโดยใช้ภาษาที่คอมไพล์เช่น C, C ++ หรือ Java สามารถรวมหรือนำเข้าในสคริปต์ Python อื่นได้ รหัสนี้ถือเป็น "ส่วนขยาย"
โมดูลส่วนขยาย Python ไม่มีอะไรมากไปกว่าไลบรารี C ปกติ ในเครื่อง Unix ไลบรารีเหล่านี้มักจะลงท้ายด้วย.so(สำหรับวัตถุที่ใช้ร่วมกัน) ในเครื่อง Windows โดยทั่วไปคุณจะเห็น.dll (สำหรับไลบรารีที่เชื่อมโยงแบบไดนามิก)
ข้อกำหนดเบื้องต้นสำหรับการเขียนส่วนขยาย
ในการเริ่มเขียนนามสกุลคุณจะต้องมีไฟล์ส่วนหัวของ Python
บนเครื่อง Unix นี้มักจะต้องติดตั้งแพคเกจสำหรับนักพัฒนาที่เฉพาะเจาะจงเช่นpython2.5-dev
ผู้ใช้ Windows จะได้รับส่วนหัวเหล่านี้เป็นส่วนหนึ่งของแพ็กเกจเมื่อใช้โปรแกรมติดตั้งไบนารี Python
นอกจากนี้สมมติว่าคุณมีความรู้เกี่ยวกับ C หรือ C ++ เป็นอย่างดีในการเขียน Python Extension โดยใช้การเขียนโปรแกรม C
ขั้นแรกให้ดูที่ส่วนขยาย Python
สำหรับการดูโมดูลส่วนขยาย Python เป็นครั้งแรกคุณต้องจัดกลุ่มรหัสของคุณเป็นสี่ส่วน -
ไฟล์ส่วนหัวPython.h .
ฟังก์ชัน C ที่คุณต้องการแสดงเป็นส่วนต่อประสานจากโมดูลของคุณ
ตารางที่แมปชื่อฟังก์ชันของคุณเมื่อนักพัฒนา Python มองว่าเป็นฟังก์ชัน C ภายในโมดูลส่วนขยาย
ฟังก์ชันการเริ่มต้น
ไฟล์ส่วนหัวPython.h
คุณต้องรวมไฟล์ส่วนหัวPython.hไว้ในไฟล์ต้นฉบับ C ของคุณซึ่งช่วยให้คุณสามารถเข้าถึง Python API ภายในที่ใช้เชื่อมต่อโมดูลของคุณเข้ากับล่าม
อย่าลืมใส่ Python.h ก่อนส่วนหัวอื่น ๆ ที่คุณอาจต้องการ คุณต้องทำตามรวมด้วยฟังก์ชันที่คุณต้องการเรียกใช้จาก Python
ฟังก์ชั่น C
ลายเซ็นของการใช้งาน C ในฟังก์ชันของคุณจะใช้รูปแบบใดรูปแบบหนึ่งในสามรูปแบบต่อไปนี้เสมอ -
static PyObject *MyFunction( PyObject *self, PyObject *args );
static PyObject *MyFunctionWithKeywords(PyObject *self,
PyObject *args,
PyObject *kw);
static PyObject *MyFunctionWithNoArgs( PyObject *self );
การประกาศก่อนหน้าแต่ละรายการส่งคืนวัตถุ Python ไม่มีสิ่งที่เรียกว่าฟังก์ชันโมฆะใน Python เนื่องจากมีอยู่ใน C หากคุณไม่ต้องการให้ฟังก์ชันของคุณส่งคืนค่าให้คืนค่า C ที่เทียบเท่ากับ PythonNoneมูลค่า. ส่วนหัว Python กำหนดมาโคร Py_RETURN_NONE ที่ทำสิ่งนี้ให้เรา
ชื่อของฟังก์ชัน C ของคุณอาจเป็นชื่ออะไรก็ได้ตามที่คุณต้องการเนื่องจากไม่มีให้เห็นนอกโมดูลส่วนขยาย พวกเขาถูกกำหนดให้เป็นฟังก์ชันคงที่
โดยปกติฟังก์ชัน C ของคุณจะถูกตั้งชื่อโดยการรวมโมดูล Python และชื่อฟังก์ชันเข้าด้วยกันดังที่แสดงไว้ที่นี่ -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
นี้เป็นฟังก์ชั่นที่เรียกว่างูหลามfuncภายในของโมดูลโมดูล คุณจะใส่พอยน์เตอร์ให้กับฟังก์ชัน C ของคุณลงในตารางวิธีการสำหรับโมดูลที่มักจะมาถัดไปในซอร์สโค้ดของคุณ
ตารางการแมปวิธีการ
ตารางวิธีนี้เป็นอาร์เรย์ของโครงสร้าง PyMethodDef อย่างง่าย โครงสร้างนั้นมีลักษณะดังนี้ -
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
นี่คือคำอธิบายของสมาชิกของโครงสร้างนี้ -
ml_name - นี่คือชื่อของฟังก์ชันตามที่ล่าม Python นำเสนอเมื่อใช้ในโปรแกรม Python
ml_meth - ต้องเป็นที่อยู่ของฟังก์ชันที่มีลายเซ็นใด ๆ ที่อธิบายไว้ในส่วนก่อนหน้านี้
ml_flags - สิ่งนี้จะบอกล่ามว่าใช้ ml_meth สามลายเซ็นใด
แฟล็กนี้มักมีค่า METH_VARARGS
แฟล็กนี้สามารถเป็นแบบบิตหรือด้วย METH_KEYWORDS หากคุณต้องการอนุญาตให้ใช้อาร์กิวเมนต์คำหลักในฟังก์ชันของคุณ
นอกจากนี้ยังสามารถมีค่าของ METH_NOARGS ที่ระบุว่าคุณไม่ต้องการยอมรับข้อโต้แย้งใด ๆ
ml_doc - นี่คือ docstring สำหรับฟังก์ชันซึ่งอาจเป็น NULL หากคุณไม่รู้สึกอยากเขียน
ตารางนี้ต้องถูกยกเลิกด้วย Sentinel ที่ประกอบด้วยค่า NULL และ 0 สำหรับสมาชิกที่เหมาะสม
ตัวอย่าง
สำหรับฟังก์ชันที่กำหนดไว้ข้างต้นเรามีตารางการแมปเมธอดต่อไปนี้ -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
ฟังก์ชัน Initialization
ส่วนสุดท้ายของโมดูลส่วนขยายของคุณคือฟังก์ชันการเริ่มต้น ฟังก์ชันนี้ถูกเรียกโดย Python interpreter เมื่อโหลดโมดูล จำเป็นต้องมีการตั้งชื่อฟังก์ชันinitModuleโดยที่โมดูลคือชื่อของโมดูล
ฟังก์ชันการเริ่มต้นจำเป็นต้องส่งออกจากไลบรารีที่คุณจะสร้าง ส่วนหัว Python กำหนด PyMODINIT_FUNC เพื่อรวมคาถาที่เหมาะสมสำหรับสิ่งที่จะเกิดขึ้นสำหรับสภาพแวดล้อมเฉพาะที่เรากำลังรวบรวม สิ่งที่คุณต้องทำคือใช้เมื่อกำหนดฟังก์ชัน
ฟังก์ชันการเริ่มต้น C ของคุณโดยทั่วไปมีโครงสร้างโดยรวมดังต่อไปนี้ -
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
นี่คือคำอธิบายของฟังก์ชันPy_InitModule3 -
func - นี่คือฟังก์ชันที่จะส่งออก
module_methods - นี่คือชื่อตารางการแมปที่กำหนดไว้ด้านบน
docstring - นี่คือความคิดเห็นที่คุณต้องการแสดงในส่วนขยายของคุณ
การรวมสิ่งนี้ทั้งหมดเข้าด้วยกันมีลักษณะดังนี้ -
#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...");
}
ตัวอย่าง
ตัวอย่างง่ายๆที่ใช้ประโยชน์จากแนวคิดข้างต้นทั้งหมด -
#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!");
}
ที่นี่มีการใช้ฟังก์ชันPy_BuildValueเพื่อสร้างค่า Python บันทึกโค้ดด้านบนในไฟล์ hello.c เราจะดูวิธีการคอมไพล์และติดตั้งโมดูลนี้เพื่อเรียกใช้จากสคริปต์ Python
การสร้างและติดตั้งส่วนขยาย
distutilsแพคเกจจะทำให้มันง่ายมากที่จะแจกจ่ายโมดูลหลามทั้งบริสุทธิ์หลามและการขยายโมดูลในวิธีการมาตรฐาน โมดูลจะกระจายในรูปแบบซอร์สและสร้างและติดตั้งผ่านสคริปต์การตั้งค่าที่มักเรียกว่าsetup.pyดังต่อไปนี้
สำหรับโมดูลข้างต้นคุณต้องเตรียมสคริปต์ setup.py ต่อไปนี้ -
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0', \
ext_modules=[Extension('helloworld', ['hello.c'])])
ตอนนี้ใช้คำสั่งต่อไปนี้ซึ่งจะดำเนินการขั้นตอนการคอมไพเลอร์และการเชื่อมโยงที่จำเป็นทั้งหมดด้วยคำสั่งและแฟล็กคอมไพเลอร์และลิงค์เกอร์ที่ถูกต้องและคัดลอกไลบรารีไดนามิกที่เป็นผลลัพธ์ไปยังไดเร็กทอรีที่เหมาะสม
$ python setup.py install
ในระบบที่ใช้ Unix คุณมักจะต้องเรียกใช้คำสั่งนี้ในฐานะรูทเพื่อให้มีสิทธิ์ในการเขียนลงในไดเร็กทอรี site-package โดยปกติแล้วจะไม่มีปัญหาใน Windows
การนำเข้าส่วนขยาย
เมื่อคุณติดตั้งส่วนขยายของคุณคุณจะสามารถนำเข้าและเรียกส่วนขยายนั้นในสคริปต์ Python ของคุณได้ดังนี้ -
#!/usr/bin/python
import helloworld
print helloworld.helloworld()
สิ่งนี้จะให้ผลลัพธ์ดังต่อไปนี้ -
Hello, Python extensions!!
พารามิเตอร์ฟังก์ชันการส่งผ่าน
เนื่องจากคุณมักต้องการกำหนดฟังก์ชันที่ยอมรับอาร์กิวเมนต์คุณสามารถใช้ลายเซ็นอื่นสำหรับฟังก์ชัน C ของคุณได้ ตัวอย่างเช่นฟังก์ชันต่อไปนี้ที่ยอมรับพารามิเตอร์จำนวนหนึ่งจะถูกกำหนดเช่นนี้ -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args and do something interesting here. */
Py_RETURN_NONE;
}
ตารางวิธีการที่มีรายการสำหรับฟังก์ชันใหม่จะมีลักษณะดังนี้ -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
คุณสามารถใช้ฟังก์ชันAPI PyArg_ParseTupleเพื่อแยกอาร์กิวเมนต์จากตัวชี้ PyObject หนึ่งตัวที่ส่งผ่านไปยังฟังก์ชัน C ของคุณ
อาร์กิวเมนต์แรกของ PyArg_ParseTuple คืออาร์กิวเมนต์ args นี่คือวัตถุที่คุณจะได้รับการแยก อาร์กิวเมนต์ที่สองคือสตริงรูปแบบที่อธิบายอาร์กิวเมนต์ตามที่คุณคาดหวังให้ปรากฏ แต่ละอาร์กิวเมนต์จะแสดงด้วยอักขระอย่างน้อยหนึ่งตัวในสตริงรูปแบบดังนี้
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;
}
การรวบรวมเวอร์ชันใหม่ของโมดูลของคุณและการนำเข้าช่วยให้คุณสามารถเรียกใช้ฟังก์ชันใหม่โดยใช้อาร์กิวเมนต์ประเภทใดก็ได้ -
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)
คุณอาจมีรูปแบบต่างๆมากขึ้น
PyArg_ParseTupleฟังก์ชั่น
นี่คือลายเซ็นมาตรฐานสำหรับ PyArg_ParseTuple ฟังก์ชัน -
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
ฟังก์ชันนี้จะคืนค่า 0 สำหรับข้อผิดพลาดและค่าไม่เท่ากับ 0 สำหรับความสำเร็จ tuple คือ PyObject * ซึ่งเป็นอาร์กิวเมนต์ที่สองของฟังก์ชัน C รูปแบบที่นี่คือสตริง C ที่อธิบายอาร์กิวเมนต์ที่จำเป็นและเป็นทางเลือก
นี่คือรายการรหัสรูปแบบสำหรับ PyArg_ParseTuple ฟังก์ชัน -
รหัส | ประเภท C | ความหมาย |
---|---|---|
ค | ถ่าน | สตริง Python ที่มีความยาว 1 กลายเป็น C char |
ง | สองเท่า | Python float กลายเป็น C double |
ฉ | ลอย | Python float กลายเป็น C float |
ผม | int | Python int กลายเป็น C int |
ล | ยาว | Python int กลายเป็น C ยาว |
ล | ยาวนาน | Python int กลายเป็น C long |
โอ | PyObject * | รับการอ้างอิงที่ยืมมาแบบไม่เป็นโมฆะไปยังอาร์กิวเมนต์ Python |
เอส | ถ่าน * | สตริง Python ที่ไม่มี nulls ฝังลงใน C char * |
s # | ถ่าน * + int | สตริง Python เป็นที่อยู่และความยาว C |
เสื้อ # | ถ่าน * + int | บัฟเฟอร์ส่วนเดียวแบบอ่านอย่างเดียวถึงที่อยู่และความยาว C |
ยู | Py_UNICODE * | Python Unicode ที่ไม่มี nulls ฝังเป็น C |
ยู# | Py_UNICODE * + int | ที่อยู่และความยาวของ Python Unicode C |
w # | ถ่าน * + int | อ่าน / เขียนบัฟเฟอร์ส่วนเดียวไปยังที่อยู่และความยาว C |
z | ถ่าน * | เช่นเดียวกับ s ยอมรับว่าไม่มี (ตั้งค่า C char * เป็น NULL) |
z # | ถ่าน * + int | เช่นเดียวกับ s # ยอมรับว่าไม่มี (ตั้งค่า C char * เป็น NULL) |
(... ) | ตาม ... | ลำดับ Python จะถือว่าเป็นหนึ่งอาร์กิวเมนต์ต่อรายการ |
| | อาร์กิวเมนต์ต่อไปนี้เป็นทางเลือก | |
: | สิ้นสุดรูปแบบตามด้วยชื่อฟังก์ชันสำหรับข้อความแสดงข้อผิดพลาด | |
; | สิ้นสุดรูปแบบตามด้วยข้อความแสดงข้อผิดพลาดทั้งหมด |
การคืนค่า
Py_BuildValueใช้รูปแบบสตริงเหมือนกับที่PyArg_ParseTupleทำ แทนที่จะส่งที่อยู่ของค่าที่คุณกำลังสร้างคุณจะส่งผ่านค่าจริง นี่คือตัวอย่างที่แสดงวิธีใช้ฟังก์ชันเพิ่ม -
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);
}
นี่คือสิ่งที่ดูเหมือนว่าถ้าใช้ใน Python -
def add(a, b):
return (a + b)
คุณสามารถคืนค่าสองค่าจากฟังก์ชันของคุณดังต่อไปนี้ซึ่งจะถูกเก็บรักษาโดยใช้รายการใน 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);
}
นี่คือสิ่งที่ดูเหมือนว่าถ้าใช้ใน Python -
def add_subtract(a, b):
return (a + b, a - b)
Py_BuildValueฟังก์ชั่น
นี่คือลายเซ็นมาตรฐานสำหรับ Py_BuildValue ฟังก์ชัน -
PyObject* Py_BuildValue(char* format,...)
รูปแบบที่นี่คือสตริง C ที่อธิบายถึงวัตถุ Python ที่จะสร้าง อาร์กิวเมนต์ของPy_BuildValueต่อไปนี้คือค่า C ที่สร้างผลลัพธ์ PyObject *ผลคือการอ้างอิงใหม่
ตารางต่อไปนี้แสดงรายการสตริงรหัสที่ใช้กันทั่วไปซึ่งรวมศูนย์หรือมากกว่าในรูปแบบสตริง
รหัส | ประเภท C | ความหมาย |
---|---|---|
ค | ถ่าน | AC char กลายเป็นสตริง Python ที่มีความยาว 1 |
ง | สองเท่า | AC double จะกลายเป็น Python float |
ฉ | ลอย | ลูกลอย AC กลายเป็นลูกลอย Python |
ผม | int | AC int กลายเป็น Python int |
ล | ยาว | AC ยาวกลายเป็น Python int |
น | PyObject * | ส่งผ่านวัตถุ Python และขโมยข้อมูลอ้างอิง |
โอ | PyObject * | ส่งผ่านวัตถุ Python และเพิ่มขึ้นตามปกติ |
O & | แปลง + โมฆะ * | การแปลงโดยพลการ |
เอส | ถ่าน * | C 0 สิ้นสุด char * เป็นสตริง Python หรือ NULL ถึง None |
s # | ถ่าน * + int | C char * และความยาวเป็นสตริง Python หรือ NULL ถึง None |
ยู | Py_UNICODE * | C-wide สตริงที่สิ้นสุดด้วย null เป็น Python Unicode หรือ NULL ถึง None |
ยู# | Py_UNICODE * + int | สตริงกว้าง C และความยาวเป็น Python Unicode หรือ NULL ถึง None |
w # | ถ่าน * + int | อ่าน / เขียนบัฟเฟอร์ส่วนเดียวไปยังที่อยู่และความยาว C |
z | ถ่าน * | เช่นเดียวกับ s ยอมรับว่าไม่มี (ตั้งค่า C char * เป็น NULL) |
z # | ถ่าน * + int | เช่นเดียวกับ s # ยอมรับว่าไม่มี (ตั้งค่า C char * เป็น NULL) |
(... ) | ตาม ... | สร้าง Python tuple จากค่า C |
[... ] | ตาม ... | สร้างรายการ Python จากค่า C |
{... } | ตาม ... | สร้างพจนานุกรม Python จากค่า C สลับคีย์และค่า |
โค้ด {... } สร้างพจนานุกรมจากค่า C จำนวนคู่สลับกันระหว่างคีย์และค่า ตัวอย่างเช่น Py_BuildValue ("{issi}", 23, "zig", "zag", 42) จะส่งคืนพจนานุกรมเช่น {23: 'zig', 'zag': 42} ของ Python