Pemrograman Ekstensi Python dengan C: Bangun Modul Asli Cepat
Halaman Sebelumnya Halaman Berikutnya
Kode apa pun yang Anda tulis menggunakan bahasa terkompilasi seperti C, C++, atau Java dapat diintegrasikan atau diimpor ke dalam skrip Python lain. Kode ini dianggap sebagai "ekstensi."
Sebuah modul ekstensi Python tidak lebih dari perpustakaan C biasa. Pada mesin Unix, perpustakaan ini biasanya berakhir dengan .so (untuk objek bersama). Pada mesin Windows, biasanya Anda melihat .dll (untuk perpustakaan yang ditautkan secara dinamis).
Prasyarat untuk Menulis Ekstensi
Untuk mulai menulis ekstensi Anda, Anda akan membutuhkan file header Python.
Pada mesin Unix, ini biasanya memerlukan instalasi paket spesifik pengembang seperti python2.5-dev.
Pengguna Windows mendapatkan header ini sebagai bagian dari paket saat mereka menggunakan penginstal biner Python.
Selain itu, diasumsikan Anda memiliki pengetahuan yang baik tentang C atau C++ untuk menulis Ekstensi Python apa pun menggunakan pemrograman C.
Pertama Kali Melihat Ekstensi Python
Untuk pertama kalinya melihat modul ekstensi Python, Anda perlu mengelompokkan kode Anda ke dalam empat bagian −
File Header Python.h
Anda perlu menyertakan file header Python.h dalam file sumber C Anda, yang memberi Anda akses ke API internal Python yang digunakan untuk menghubungkan modul Anda ke interpreter.
Pastikan untuk menyertakan Python.h sebelum header lain yang mungkin Anda butuhkan. Anda perlu mengikuti includes dengan fungsi yang ingin Anda panggil dari Python.
Fungsi C
Tanda tangan implementasi C dari fungsi Anda selalu mengambil salah satu dari tiga bentuk berikut −
static PyObject *MyFunction( PyObject *self, PyObject *args ); static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw); static PyObject *MyFunctionWithNoArgs( PyObject *self );
Setiap deklarasi di atas mengembalikan objek Python. Tidak ada hal yang disebut fungsi void dalam Python seperti yang ada di C. Jika Anda tidak ingin fungsi Anda mengembalikan nilai, kembalikan setara C dari nilai None Python. Header Python mendefinisikan makro, Py_RETURN_NONE, yang melakukan ini untuk kita.
Nama fungsi C Anda dapat apa pun yang Anda sukai karena tidak pernah dilihat di luar modul ekstensi. Mereka didefinisikan sebagai fungsi static.
Fungsi C Anda biasanya dinamakan dengan menggabungkan nama modul dan fungsi Python bersama-sama, seperti yang ditunjukkan di sini −
static PyObject *module_func(PyObject *self, PyObject *args) { /* Lakukan hal Anda di sini. */ Py_RETURN_NONE; } Ini adalah fungsi Python yang disebut func di dalam modul module. Anda akan meletakkan pointer ke fungsi C Anda ke dalam tabel metode untuk modul yang biasanya datang selanjutnya dalam kode sumber Anda.
Tabel Pemetaan Metode
Tabel metode ini adalah array sederhana dari struktur PyMethodDef. Struktur tersebut terlihat seperti ini −
struct PyMethodDef { char *ml_name; PyCFunction ml_meth; int ml_flags; char *ml_doc; }; Berikut adalah deskripsi anggota struktur ini −
ml_name − Ini adalah nama fungsi seperti yang ditampilkan oleh interpreter Python saat digunakan dalam program Python.
ml_meth − Ini harus merupakan alamat ke fungsi yang memiliki salah satu tanda tangan yang dijelaskan di bagian sebelumnya.
ml_flags − Ini memberitahu interpreter salah satu dari tiga tanda tangan yang digunakan ml_meth.
Flag ini biasanya memiliki nilai METH_VARARGS.
Flag ini dapat di-OR bitwise dengan METH_KEYWORDS jika Anda ingin mengizinkan argumen kata kunci ke dalam fungsi Anda.
Ini juga dapat memiliki nilai METH_NOARGS yang menunjukkan Anda tidak ingin menerima argumen apa pun.
ml_doc − Ini adalah docstring untuk fungsi, yang dapat NULL jika Anda tidak merasa ingin menulisnya.
Tabel ini perlu diakhiri dengan sentinel yang terdiri dari nilai NULL dan 0 untuk anggota yang sesuai.
Contoh
Untuk fungsi yang telah didefinisikan di atas, kita memiliki tabel pemetaan metode berikut −
static PyMethodDef module_methods[] = { { "func", (PyCFunction)module_func, METH_NOARGS, NULL }, { NULL, NULL, 0, NULL } }; Fungsi Inisialisasi
Bagian terakhir dari modul ekstensi Anda adalah fungsi inisialisasi. Fungsi ini dipanggil oleh interpreter Python saat modul dimuat. Diperlukan agar fungsi dinamakan initModule, di mana Module adalah nama modul.
Fungsi inisialisasi perlu diekspor dari perpustakaan yang akan Anda bangun. Header Python mendefinisikan PyMODINIT_FUNC untuk menyertakan mantra yang sesuai agar ini dapat terjadi untuk lingkungan tertentu tempat kita mengompilasi. Yang perlu Anda lakukan adalah menggunakannya saat mendefinisikan fungsi.
Fungsi inisialisasi C Anda umumnya memiliki struktur keseluruhan berikut −
PyMODINIT_FUNC initModule() { Py_InitModule3(func, module_methods, "docstring..."); } Berikut adalah deskripsi fungsi Py_InitModule3 −
func − Ini adalah fungsi yang akan diekspor.
module_methods − Ini adalah nama tabel pemetaan yang didefinisikan di atas.
docstring − Ini adalah komentar yang ingin Anda berikan dalam ekstensi Anda.
Menggabungkan semuanya terlihat seperti berikut −
#include <Python.h> static PyObject *module_func(PyObject *self, PyObject *args) { /* Lakukan hal Anda di sini. */ 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..."); } Contoh
Contoh sederhana yang menggunakan semua konsep di atas −
#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!"); } Di sini fungsi Py_BuildValue digunakan untuk membuat nilai Python. Simpan kode di atas dalam file hello.c. Kita akan melihat cara mengompilasi dan menginstal modul ini untuk dipanggil dari skrip Python.
Membangun dan Menginstal Ekstensi
Paket distutils membuatnya sangat mudah untuk mendistribusikan modul Python, baik murni Python maupun modul ekstensi, dengan cara standar. Modul didistribusikan dalam bentuk sumber dan dibangun serta diinstal melalui skrip setup yang biasanya disebut setup.py sebagai berikut.
Untuk modul di atas, Anda perlu menyiapkan skrip setup.py berikut −
from distutils.core import setup, Extension setup(name='helloworld', version='1.0', \ ext_modules=[Extension('helloworld', ['hello.c'])]) Sekarang, gunakan perintah berikut, yang akan melakukan semua langkah kompilasi dan penautan yang diperlukan, dengan perintah dan flag kompiler serta penaut yang tepat, dan menyalin perpustakaan dinamis yang dihasilkan ke direktori yang sesuai −
$ python setup.py install
Pada sistem berbasis Unix, kemungkinan besar Anda perlu menjalankan perintah ini sebagai root untuk memiliki izin menulis ke direktori site-packages. Biasanya ini bukan masalah pada Windows.
Mengimpor Ekstensi
Setelah Anda menginstal ekstensi Anda, Anda akan dapat mengimpor dan memanggil ekstensi tersebut dalam skrip Python Anda sebagai berikut −
#!/usr/bin/python import helloworld print helloworld.helloworld()
Ini akan menghasilkan hasil berikut −
Hello, Python extensions!!
Melewatkan Parameter Fungsi
Seperti yang Anda kemungkinan inginkan untuk mendefinisikan fungsi yang menerima argumen, Anda dapat menggunakan salah satu tanda tangan lain untuk fungsi C Anda. Misalnya, fungsi berikut, yang menerima sejumlah parameter, akan didefinisikan seperti ini −
static PyObject *module_func(PyObject *self, PyObject *args) { /* Parse args dan lakukan sesuatu yang menarik di sini. */ Py_RETURN_NONE; } Tabel metode yang berisi entri untuk fungsi baru akan terlihat seperti ini −
static PyMethodDef module_methods[] = { { "func", (PyCFunction)module_func, METH_NOARGS, NULL }, { "func", module_func, METH_VARARGS, NULL }, { NULL, NULL, 0, NULL } }; Anda dapat menggunakan fungsi API PyArg_ParseTuple untuk mengekstrak argumen dari pointer PyObject yang diteruskan ke dalam fungsi C Anda.
Argumen pertama untuk PyArg_ParseTuple adalah argumen args. Ini adalah objek yang akan Anda parsing. Argumen kedua adalah string format yang menggambarkan argumen seperti yang Anda harapkan mereka muncul. Setiap argumen direpresentasikan oleh satu atau lebih karakter dalam string format sebagai berikut.
static PyObject *module_func(PyObject *self, PyObject *args) { int i; double d; char *s; if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) { return NULL; } /* Lakukan sesuatu yang menarik di sini. */ Py_RETURN_NONE; } Mengompilasi versi baru modul Anda dan mengimpornya memungkinkan Anda untuk memanggil fungsi baru dengan sejumlah argumen apa pun dari tipe apa pun −
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)
Anda mungkin dapat menghasilkan variasi lebih banyak lagi.
Fungsi PyArg_ParseTuple
Berikut adalah tanda tangan standar untuk fungsi PyArg_ParseTuple −
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
Fungsi ini mengembalikan 0 untuk error, dan nilai yang tidak sama dengan 0 untuk kesuksesan. tuple adalah PyObject* yang merupakan argumen kedua dari fungsi C. Di sini format adalah string C yang menggambarkan argumen wajib dan opsional.
Berikut adalah daftar kode format untuk fungsi PyArg_ParseTuple −
| Kode | Tipe C | Arti |
| c | char | Sebuah string Python panjang 1 menjadi C char. |
| d | double | Sebuah float Python menjadi C double. |
| f | float | Sebuah float Python menjadi C float. |
| i | int | Sebuah int Python menjadi C int. |
| l | long | Sebuah int Python menjadi C long. |
| L | long long | Sebuah int Python menjadi C long long |
| O | PyObject* | Mendapatkan referensi pinjaman non-NULL ke argumen Python. |
| s | char* | String Python tanpa null tersemat ke C char*. |
| s# | char*+int | String Python apa pun ke alamat dan panjang C. |
| t# | char*+int | Buffer segmen tunggal hanya baca ke alamat dan panjang C. |
| u | Py_UNICODE* | Python Unicode tanpa null tersemat ke C. |
| u# | Py_UNICODE*+int | Python Unicode apa pun alamat dan panjang C. |
| w# | char*+int | Buffer segmen tunggal baca/tulis ke alamat dan panjang C. |
| z | char* | Seperti s, juga menerima None (mengatur C char* ke NULL). |
| z# | char*+int | Seperti s#, juga menerima None (mengatur C char* ke NULL). |
| (...) | as per ... | Urutan Python diperlakukan sebagai satu argumen per item. |
| | | | Argumen berikutnya adalah opsional. |
| : | | Akhir format, diikuti oleh nama fungsi untuk pesan error. |
| ; | | Akhir format, diikuti oleh seluruh teks pesan error. |
Mengembalikan Nilai
Py_BuildValue menerima string format seperti yang dilakukan PyArg_ParseTuple. Alih-alih meneruskan alamat nilai yang Anda bangun, Anda meneruskan nilai aktual. Berikut adalah contoh yang menunjukkan cara mengimplementasikan fungsi tambah −
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); } Ini adalah apa yang akan terlihat jika diimplementasikan dalam Python −
def add(a, b): return (a + b)
Anda dapat mengembalikan dua nilai dari fungsi Anda sebagai berikut, ini akan ditangkap menggunakan daftar dalam 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); } Ini adalah apa yang akan terlihat jika diimplementasikan dalam Python −
def add_subtract(a, b): return (a + b, a - b)
Fungsi Py_BuildValue
Berikut adalah tanda tangan standar untuk fungsi Py_BuildValue −
PyObject* Py_BuildValue(char* format,...)
Di sini format adalah string C yang menggambarkan objek Python yang akan dibuat. Argumen berikutnya dari Py_BuildValue adalah nilai C yang digunakan untuk membangun hasil. Hasil PyObject* adalah referensi baru.
Tabel berikut mencantumkan string kode yang sering digunakan, nol atau lebih digabungkan menjadi string format.
| Kode | Tipe C | Arti |
| c | char | Sebuah C char menjadi string Python panjang 1. |
| d | double | Sebuah C double menjadi float Python. |
| f | float | Sebuah C float menjadi float Python. |
| i | int | Sebuah C int menjadi int Python. |
| l | long | Sebuah C long menjadi int Python. |
| N | PyObject* | Mengirim objek Python dan mencuri referensi. |
| O | PyObject* | Mengirim objek Python dan INCREF-nya seperti biasa. |
| O& | convert+void* | Konversi sembarang |
| s | char* | C 0-terminated char* ke string Python, atau NULL ke None. |
| s# | char*+int | C char* dan panjang ke string Python, atau NULL ke None. |
| u | Py_UNICODE* | String C-wide, null-terminated ke Unicode Python, atau NULL ke None. |
| u# | Py_UNICODE*+int | String C-wide dan panjang ke Unicode Python, atau NULL ke None. |
| w# | char*+int | Buffer segmen tunggal baca/tulis ke alamat dan panjang C. |
| z | char* | Seperti s, juga menerima None (mengatur C char* ke NULL). |
| z# | char*+int | Seperti s#, juga menerima None (mengatur C char* ke NULL). |
| (...) | as per ... | Membangun tuple Python dari nilai C. |
| [...] | as per ... | Membangun daftar Python dari nilai C. |
| {...} | as per ... | Membangun kamus Python dari nilai C, secara bergantian kunci dan nilai. |
Kode {...} membangun kamus dari sejumlah nilai C yang genap, secara bergantian kunci dan nilai. Misalnya, Py_BuildValue("{issi}",23,"zig","zag",42) mengembalikan kamus seperti Python {23:'zig','zag':42}.