Python 3 - C ile Uzantı Programlama

C, C ++ veya Java gibi herhangi bir derlenmiş dili kullanarak yazdığınız herhangi bir kod, başka bir Python betiğine entegre edilebilir veya içe aktarılabilir. Bu kod bir "uzantı" olarak kabul edilir.

Bir Python uzantı modülü, normal bir C kitaplığından başka bir şey değildir. Unix makinelerde, bu kitaplıklar genellikle.so(paylaşılan nesne için). Windows makinelerde genellikle şunu görürsünüz:.dll (dinamik bağlantılı kitaplık için).

Uzantıları Yazmak İçin Ön Koşullar

Uzantınızı yazmaya başlamak için Python başlık dosyalarına ihtiyacınız olacak.

  • Unix makinelerinde, bu genellikle geliştiriciye özel bir paketin yüklenmesini gerektirir.

  • Windows kullanıcıları bu başlıkları ikili Python yükleyicisini kullandıklarında paketin bir parçası olarak alırlar.

Ek olarak, C programlamayı kullanarak herhangi bir Python Uzantısını yazmak için iyi bir C veya C ++ bilgisine sahip olduğunuz varsayılır.

İlk olarak bir Python Uzantısına bakın

Bir Python genişletme modülüne ilk bakışınız için, kodunuzu dört bölüme ayırmanız gerekir -

  • Python.h başlık dosyası .

  • Modülünüzden arayüz olarak ortaya çıkarmak istediğiniz C fonksiyonları.

  • Python geliştiricilerinin uzantı modülünün içinde C işlevleri olarak gördükleri için işlevlerinizin adlarını eşleştiren bir tablo.

  • Bir başlatma işlevi.

Başlık Dosyası Python.h

Python.h başlık dosyasını C kaynak dosyanıza eklemeniz gerekir , bu da modülünüzü yorumlayıcıya bağlamak için kullanılan dahili Python API'sine erişim sağlar.

İhtiyaç duyabileceğiniz diğer başlıklardan önce Python.h'yi eklediğinizden emin olun. Python'dan çağırmak istediğiniz fonksiyonlar ile içerilenleri takip etmeniz gerekiyor.

C İşlevleri

İşlevlerinizin C uygulamasının imzaları her zaman aşağıdaki üç biçimden birini alır:

static PyObject *MyFunction( PyObject *self, PyObject *args );

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

static PyObject *MyFunctionWithNoArgs( PyObject *self );

Önceki bildirimlerin her biri bir Python nesnesi döndürür. Python'da C'de olduğu gibi void işlevi diye bir şey yoktur . İşlevlerinizin bir değer döndürmesini istemiyorsanız, Python'un C eşdeğerini döndürün.Nonedeğer. Python başlıkları bunu bizim için yapan bir Py_RETURN_NONE makrosu tanımlar.

C işlevlerinizin adları, uzantı modülünün dışında asla görülmediklerinden istediğiniz gibi olabilir. Statik fonksiyon olarak tanımlanırlar .

C işlevleriniz genellikle Python modülü ve işlev adlarını burada gösterildiği gibi bir araya getirerek adlandırılır -

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

Bu, modül modülünün içinde func adı verilen bir Python işlevidir . Genellikle kaynak kodunuzda sonraki sırada gelen modülün yöntem tablosuna C işlevlerinize işaretçiler koyacaksınız.

Yöntem Eşleme Tablosu

Bu yöntem tablosu, PyMethodDef yapılarının basit bir dizisidir. Bu yapı şuna benzer -

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

İşte bu yapının üyelerinin açıklaması -

  • ml_name - Bu, Python yorumlayıcısının Python programlarında kullanıldığında sunduğu gibi işlevin adıdır.

  • ml_meth - Bu, önceki bölümde açıklanan imzalardan herhangi birine sahip bir işlevin adresidir.

  • ml_flags - Bu, yorumlayıcıya ml_meth'in üç imzadan hangisini kullandığını söyler.

    • Bu bayrak genellikle METH_VARARGS değerine sahiptir.

    • Fonksiyonunuzda anahtar kelime argümanlarına izin vermek istiyorsanız, bu bayrak METH_KEYWORDS ile bitsel OR'ed olabilir.

    • Bu aynı zamanda herhangi bir argüman kabul etmek istemediğinizi gösteren bir METH_NOARGS değerine sahip olabilir.

  • ml_doc - Bu, işlevin docstring'idir ve bir tane yazmak istemiyorsanız NULL olabilir.

Bu tablonun, uygun üyeler için NULL ve 0 değerlerinden oluşan bir nöbetçi ile sonlandırılması gerekir.

Misal

Yukarıda tanımlanan işlev için, aşağıdaki yöntem eşleme tablosuna sahibiz -

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

Başlatma İşlevi

Genişletme modülünüzün son kısmı, başlatma işlevidir. Bu işlev, modül yüklendiğinde Python yorumlayıcısı tarafından çağrılır. Fonksiyonun adlandırılması gerekirinitModule, Modül modülün adıdır.

Başlatma işlevinin oluşturacağınız kütüphaneden dışa aktarılması gerekir. Python başlıkları, PyMODINIT_FUNC'yi, derlediğimiz belirli ortam için gerçekleşmesi için uygun büyülü sözler içerecek şekilde tanımlar. Tek yapmanız gereken, işlevi tanımlarken kullanmaktır.

C başlatma işleviniz genellikle aşağıdaki genel yapıya sahiptir -

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

İşte açıklaması Py_InitModule3 işlev -

  • func - Bu, dışa aktarılacak işlevdir.

  • module_methods - Bu, yukarıda tanımlanan eşleme tablosu adıdır.

  • docstring - Uzantınızda vermek istediğiniz yorum budur.

Tüm bunları bir araya getirirsek, aşağıdaki gibi görünür -

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

Misal

Yukarıdaki tüm kavramlardan yararlanan basit bir örnek -

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

Burada Py_BuildValue işlevi bir Python değeri oluşturmak için kullanılır. Yukarıdaki kodu merhaba.c dosyasına kaydedin. Python betiğinden çağrılacak bu modülü nasıl derleyip kuracağımızı göreceğiz.

Uzantıları Oluşturma ve Yükleme

Distutils paketi çok kolay standart bir şekilde, Python modülleri, hem saf Python ve uzatma modüllerini dağıtmak için yapar. Modüller kaynak biçiminde dağıtılır, genellikle setup.py olarak adı verilen bir kurulum betiği aracılığıyla oluşturulur ve yüklenir .

Yukarıdaki modül için aşağıdaki setup.py betiğini hazırlamanız gerekir -

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

Şimdi, gerekli tüm derleme ve bağlama adımlarını doğru derleyici ve bağlayıcı komutları ve bayrakları ile gerçekleştirecek aşağıdaki komutu kullanın ve elde edilen dinamik kitaplığı uygun bir dizine kopyalayın -

$ python setup.py install

Unix tabanlı sistemlerde, site paketleri dizinine yazma izinlerine sahip olmak için büyük olasılıkla bu komutu root olarak çalıştırmanız gerekecektir. Bu genellikle Windows'ta bir sorun değildir.

Uzantıları İçe Aktarma

Uzantılarınızı yükledikten sonra, bu uzantıyı Python betiğinize aşağıdaki gibi içe aktarabilir ve çağırabilirsiniz -

Misal

#!/usr/bin/python3
import helloworld

print helloworld.helloworld()

Çıktı

Bu, aşağıdaki sonucu verecektir -

Hello, Python extensions!!

Fonksiyon Parametrelerini Geçirme

Büyük olasılıkla bağımsız değişkenleri kabul eden işlevleri tanımlamak isteyeceğinizden, C işlevleriniz için diğer imzalardan birini kullanabilirsiniz. Örneğin, bir dizi parametreyi kabul eden aşağıdaki işlev şu şekilde tanımlanacaktır -

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

Yeni işlev için bir giriş içeren yöntem tablosu şöyle görünecektir -

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

API PyArg_ParseTuple işlevini, C işlevinize geçirilen bir PyObject işaretçisinden bağımsız değişkenleri çıkarmak için kullanabilirsiniz .

PyArg_ParseTuple için ilk argüman args argümanıdır. Bu, ayrıştıracağınız nesnedir . İkinci bağımsız değişken, görünmelerini beklediğiniz şekilde bağımsız değişkenleri açıklayan bir biçim dizesidir. Her bağımsız değişken, aşağıdaki gibi biçim dizesinde bir veya daha fazla karakterle temsil edilir.

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;
}

Çıktı

Modülünüzün yeni sürümünü derlemek ve içe aktarmak, yeni işlevi herhangi bir türden herhangi bir sayıda argümanla çağırmanıza olanak tanır -

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)

Muhtemelen daha fazla varyasyon bulabilirsin.

PyArg_ParseTuple İşlevi

Standart imza şu şekildedir: PyArg_ParseTuple işlev -

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

Bu işlev, hatalar için 0 ve başarı için 0'a eşit olmayan bir değer döndürür. Tuple, C işlevinin ikinci argümanı olan PyObject * 'dir. Burada biçim , zorunlu ve isteğe bağlı bağımsız değişkenleri tanımlayan bir C dizesidir.

Aşağıdaki format kodlarının listesi PyArg_ParseTuple işlev -

Kod C tipi Anlam
c kömür 1 uzunluğundaki bir Python dizesi C karakterine dönüşür.
d çift Bir Python şamandırası, C doubleına dönüşür.
f yüzer Bir Python şamandırası, C şamandırasına dönüşür.
ben int Python int, C int olur.
l uzun Python int, C uzunluğuna dönüşür.
L uzunca Python int, uzun C olur
Ö PyObject * Python bağımsız değişkenine NULL olmayan ödünç alınmış başvuru alır.
s karakter * C karakterine gömülü boş değerler içermeyen Python dizesi *.
s # char * + int Herhangi bir Python dizesi - C adresi ve uzunluğu.
t # char * + int C adresine ve uzunluğuna salt okunur tek bölümlü arabellek.
sen Py_UNICODE * C'ye gömülü boş değerler içermeyen Python Unicode.
u # Py_UNICODE * + int Herhangi bir Python Unicode C adresi ve uzunluğu.
w # char * + int Tek segmentli tamponu C adresine ve uzunluğuna okuyun / yazın.
z karakter * S gibi, Yok'u da kabul eder (C karakterini * NULL olarak ayarlar).
z # char * + int S # gibi, Yok'u da kabul eder (C karakterini * NULL olarak ayarlar).
(...) göre ... Bir Python dizisi, öğe başına bir bağımsız değişken olarak değerlendirilir.
| Aşağıdaki bağımsız değişkenler isteğe bağlıdır.
: Sonu biçimlendir, ardından hata mesajları için işlev adı.
; Sonu biçimlendirin, ardından tüm hata mesajı metnini takip edin.

Dönen Değerler

Py_BuildValue , PyArg_ParseTuple'ın yaptığı gibi bir biçim dizesi alır . Oluşturmakta olduğunuz değerlerin adreslerini iletmek yerine gerçek değerleri iletiyorsunuz. İşte add işlevinin nasıl uygulanacağını gösteren bir örnek -

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'da uygulandığında nasıl görüneceği budur -

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

Fonksiyonunuzdan iki değeri aşağıdaki gibi döndürebilirsiniz. Bu, Python'da bir liste kullanılarak yakalanacaktır.

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'da uygulandığında nasıl görüneceği budur -

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

Py_BuildValue İşlevi

İşte standart imza Py_BuildValue işlev -

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

Buradaki format , oluşturulacak Python nesnesini tanımlayan bir C dizesidir. Aşağıdaki Py_BuildValue bağımsız değişkenleri, sonucun oluşturulduğu C değerleridir. PyObject * sonucun yeni referanstır.

Aşağıdaki tablo, sıfır veya daha fazlası bir dize biçiminde birleştirilen yaygın olarak kullanılan kod dizelerini listeler.

Kod C tipi Anlam
c kömür AC karakter 1 uzunluğunda bir Python dizesi olur.
d çift AC double, bir Python şamandırasına dönüşür.
f yüzer AC float bir Python float'a dönüşür.
ben int AC int, Python int olur.
l uzun AC long bir Python int olur.
N PyObject * Bir Python nesnesi iletir ve bir referans çalar.
Ö PyObject * Bir Python nesnesi iletir ve onu normal bir şekilde ARTTIRIR.
Ö& dönüştür + geçersiz * Keyfi dönüşüm
s karakter * C 0-sonlandırılmış char * 'i Python dizesine veya NULL'dan Yok'a.
s # char * + int C karakter * ve uzunluk Python dizesine veya NULL'dan Yok'a.
sen Py_UNICODE * Python Unicode'a C çapında, boş sonlu dize veya NULL'dan Yok'a.
u # Py_UNICODE * + int C-geniş dize ve uzunluk Python Unicode'a veya NULL'dan Yok'a.
w # char * + int Tek segmentli tamponu C adresine ve uzunluğuna okuyun / yazın.
z karakter * S gibi, Yok'u da kabul eder (C karakterini * NULL olarak ayarlar).
z # char * + int S # gibi, Yok'u da kabul eder (C karakterini * NULL olarak ayarlar).
(...) göre ... C değerlerinden Python tuple oluşturur.
[...] göre ... C değerlerinden Python listesi oluşturur.
{...} göre ... C değerleri, alternatif anahtarlar ve değerlerden Python sözlüğü oluşturur.

Kod {...}, çift sayıda C değerinden, dönüşümlü olarak anahtarlar ve değerlerden sözlükler oluşturur. Örneğin, Py_BuildValue ("{issi}", 23, "zig", "zag", 42) Python'un {23: 'zig', 'zag': 42} gibi bir sözlük döndürür.