Python - Pemrograman Ekstensi dengan C

Kode apa pun yang Anda tulis menggunakan bahasa terkompilasi seperti C, C ++, atau Java dapat diintegrasikan atau diimpor ke skrip Python lain. Kode ini dianggap sebagai "ekstensi".

Modul ekstensi Python tidak lebih dari pustaka C biasa. Pada mesin Unix, pustaka ini biasanya diakhiri dengan.so(untuk objek bersama). Di mesin Windows, Anda biasanya melihat.dll (untuk perpustakaan yang terhubung secara dinamis).

Prasyarat untuk Menulis Ekstensi

Untuk mulai menulis ekstensi Anda, Anda memerlukan file header Python.

  • Pada mesin Unix, ini biasanya memerlukan penginstalan paket khusus pengembang seperti python2.5-dev .

  • Pengguna Windows mendapatkan header ini sebagai bagian dari paket ketika mereka menggunakan penginstal Python biner.

Selain itu, diasumsikan bahwa Anda memiliki pengetahuan yang baik tentang C atau C ++ untuk menulis Ekstensi Python apa pun menggunakan pemrograman C.

Pertama lihat Ekstensi Python

Untuk tampilan pertama Anda pada modul ekstensi Python, Anda perlu mengelompokkan kode Anda menjadi empat bagian -

  • File header Python.h .

  • Fungsi C yang ingin Anda tampilkan sebagai antarmuka dari modul Anda.

  • Tabel yang memetakan nama fungsi Anda saat developer Python melihatnya ke fungsi C di dalam modul ekstensi.

  • Fungsi inisialisasi.

File Header Python.h

Anda perlu menyertakan file header Python.h di file sumber C Anda, yang memberi Anda akses ke API Python internal yang digunakan untuk menghubungkan modul Anda ke interpreter.

Pastikan untuk menyertakan Python.h sebelum header lain yang mungkin Anda perlukan. Anda harus mengikuti penyertaan dengan fungsi yang ingin Anda panggil dari Python.

Fungsi C.

Tanda tangan implementasi C 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 sebelumnya mengembalikan objek Python. Tidak ada yang namanya fungsi void di Python seperti di C.Jika Anda tidak ingin fungsi Anda mengembalikan nilai, kembalikan persamaan C dari PythonNonenilai. Header Python mendefinisikan makro, Py_RETURN_NONE, yang melakukan ini untuk kita.

Nama fungsi C Anda bisa apa saja yang Anda suka karena tidak pernah terlihat di luar modul ekstensi. Mereka didefinisikan sebagai fungsi statis .

Fungsi C Anda biasanya dinamai dengan menggabungkan modul Python dan nama fungsi bersama, seperti yang ditunjukkan di sini -

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

Ini adalah fungsi Python yang disebut func di dalam modul modul . Anda akan meletakkan pointer ke fungsi C Anda ke dalam tabel metode untuk modul yang biasanya muncul berikutnya dalam kode sumber Anda.

Tabel Pemetaan Metode

Tabel metode ini adalah larik sederhana dari struktur PyMethodDef. Struktur itu 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 yang disajikan oleh penerjemah Python saat digunakan dalam program Python.

  • ml_meth - Ini harus berupa alamat ke fungsi yang memiliki salah satu dari tanda tangan yang dijelaskan pada bagian sebelumnya.

  • ml_flags - Ini memberi tahu penerjemah mana dari tiga tanda tangan yang digunakan ml_meth.

    • Bendera ini biasanya memiliki nilai METH_VARARGS.

    • Bendera ini dapat di-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 fungsi docstring, yang bisa menjadi NULL jika Anda tidak ingin menulisnya.

Tabel ini perlu diakhiri dengan sentinel yang terdiri dari NULL dan 0 nilai untuk anggota yang sesuai.

Contoh

Untuk fungsi yang didefinisikan di atas, kami 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 tersebut dinamaiinitModule, di mana Module adalah nama modul.

Fungsi inisialisasi perlu diekspor dari perpustakaan yang akan Anda buat. Header Python mendefinisikan PyMODINIT_FUNC untuk menyertakan mantera yang sesuai agar hal itu terjadi untuk lingkungan tertentu tempat kita menyusun. Yang harus 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 ditentukan di atas.

  • docstring - Ini adalah komentar yang ingin Anda berikan di ekstensi Anda.

Menyatukan semua ini terlihat seperti berikut -

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

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 membangun nilai Python. Simpan kode di atas dalam file hello.c. Kita akan melihat bagaimana mengkompilasi dan menginstal modul ini untuk dipanggil dari skrip Python.

Membangun dan Memasang Ekstensi

The distutils paket membuatnya sangat mudah untuk mendistribusikan modul Python, baik murni Python dan ekstensi modul, dengan cara standar. Modul didistribusikan dalam bentuk sumber dan dibangun serta dipasang melalui skrip pengaturan yang biasanya disebut setup.py sebagai berikut.

Untuk modul di atas, Anda perlu mempersiapkan 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 compiler dan linker yang tepat, dan menyalin pustaka dinamis yang dihasilkan ke direktori yang sesuai -

$ python setup.py install

Pada sistem berbasis Unix, kemungkinan besar Anda harus menjalankan perintah ini sebagai root agar memiliki izin untuk menulis ke direktori paket situs. Ini biasanya tidak menjadi masalah di Windows.

Mengimpor Ekstensi

Setelah Anda memasang ekstensi Anda, Anda akan dapat mengimpor dan memanggil ekstensi itu dalam skrip Python Anda sebagai berikut -

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

Ini akan menghasilkan hasil sebagai berikut -

Hello, Python extensions!!

Parameter Fungsi Melewati

Karena Anda kemungkinan besar ingin mendefinisikan fungsi yang menerima argumen, Anda dapat menggunakan salah satu tanda tangan lain untuk fungsi C. Misalnya, fungsi berikut, yang menerima sejumlah parameter, akan didefinisikan seperti ini -

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   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 satu penunjuk PyObject yang diteruskan ke fungsi C.

Argumen pertama untuk PyArg_ParseTuple adalah argumen args. Ini adalah objek yang akan Anda parsing . Argumen kedua adalah string format yang mendeskripsikan argumen seperti yang Anda harapkan. Setiap argumen diwakili oleh satu atau lebih karakter dalam format string 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;
   }
   
   /* Do something interesting here. */
   Py_RETURN_NONE;
}

Mengompilasi versi baru modul Anda dan mengimpornya memungkinkan Anda menjalankan fungsi baru dengan sejumlah argumen jenis 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 bisa mendapatkan lebih banyak variasi.

The PyArg_ParseTuple Fungsi

Ini adalah tanda tangan standar untuk PyArg_ParseTuple fungsi -

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

Fungsi ini mengembalikan 0 untuk kesalahan, dan nilai tidak sama dengan 0 untuk keberhasilan. tuple adalah PyObject * yang merupakan argumen kedua fungsi C. Berikut Format adalah C string yang menjelaskan argumen wajib dan opsional.

Berikut adalah daftar kode format untuk PyArg_ParseTuple fungsi -

Kode Tipe C. Berarti
c arang String Python dengan panjang 1 menjadi karakter C.
d dua kali lipat Float Python menjadi C double.
f mengapung Float Python menjadi float C.
saya int Sebuah Python int menjadi sebuah C int.
l panjang Sebuah int Python menjadi C panjang.
L Panjang panjang Sebuah int Python menjadi panjang C
HAI PyObject * Mendapat referensi pinjaman non-NULL ke argumen Python.
s arang* String Python tanpa null yang disematkan ke C char *.
s # char * + int String Python apa pun ke alamat dan panjang C.
t # char * + int Buffer segmen tunggal baca ke alamat C dan panjangnya.
u Py_UNICODE * Python Unicode tanpa null tertanam ke C.
u # Py_UNICODE * + int Alamat dan panjang Python Unicode C apa pun.
w # char * + int Baca / tulis buffer segmen tunggal ke alamat dan panjang C.
z arang* Seperti s, juga menerima None (set C char * ke NULL).
z # char * + int Seperti s #, juga menerima None (set C char * ke NULL).
(...) sesuai ... Urutan Python diperlakukan sebagai satu argumen per item.
|   Argumen berikut ini opsional.
:   Akhir format, diikuti dengan nama fungsi untuk pesan kesalahan.
;   Format akhir, diikuti oleh seluruh teks pesan kesalahan.

Mengembalikan Nilai

Py_BuildValue menggunakan format string seperti yang dilakukan PyArg_ParseTuple . Alih-alih memasukkan alamat dari nilai yang Anda bangun, Anda meneruskan nilai sebenarnya. Berikut adalah contoh yang menunjukkan bagaimana mengimplementasikan fungsi add -

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 akan terlihat seperti jika diimplementasikan dengan Python -

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

Anda dapat mengembalikan dua nilai dari fungsi Anda sebagai berikut, ini akan disimpan menggunakan daftar dengan 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 akan terlihat seperti jika diimplementasikan dengan Python -

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

The Py_BuildValue Fungsi

Ini adalah tanda tangan standar untuk Py_BuildValue fungsi -

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

Berikut Format adalah C string yang menggambarkan objek Python untuk membangun. Argumen Py_BuildValue berikut adalah nilai C dari mana hasil dibuat. Hasil PyObject * adalah referensi baru.

Tabel berikut mencantumkan string kode yang umum digunakan, di mana nol atau lebih digabungkan ke dalam format string.

Kode Tipe C. Berarti
c arang Karakter AC menjadi string Python dengan panjang 1.
d dua kali lipat AC ganda menjadi float Python.
f mengapung AC float menjadi float Python.
saya int AC int menjadi int Python.
l panjang AC lama menjadi int Python.
N PyObject * Meneruskan objek Python dan mencuri referensi.
HAI PyObject * Meneruskan objek Python dan INCREF seperti biasa.
HAI& konversikan + batal * Konversi sewenang-wenang
s arang* C 0-diakhiri 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 * C-wide, string diakhiri null ke Python Unicode, atau NULL ke None.
u # Py_UNICODE * + int String C-wide dan panjang ke Python Unicode, atau NULL ke None.
w # char * + int Baca / tulis buffer segmen tunggal ke alamat dan panjang C.
z arang* Seperti s, juga menerima None (set C char * ke NULL).
z # char * + int Seperti s #, juga menerima None (set C char * ke NULL).
(...) sesuai ... Membangun tupel Python dari nilai C.
[...] sesuai ... Membangun daftar Python dari nilai C.
{...} sesuai ... Membangun kamus Python dari nilai C, kunci dan nilai bergantian.

Kode {...} membuat kamus dari nilai C bilangan genap, sebagai alternatif kunci dan nilai. Misalnya, Py_BuildValue ("{issi}", 23, "zig", "zag", 42) mengembalikan kamus seperti {23: 'zig', 'zag': 42} Python.