Python的C/C++擴展——Python的C語言接口

文章首發於個人技術博客:你能夠在上面看到更多的Python教程python爬蟲python

Python語言最初是用C語言實現的一種腳本語言,後來被稱爲CPython,是由於後來又有其它語言實現的Python,好比Python實現的Python——PyPy,Java語言實現的Python——Jython,.Net實現的Python——IronPython。linux

CPython具備優良的開放性和可擴展性,並提供了方便靈活的應用程序接口(API),從而使得C/C++程序員可以在各個級別上對Python解釋器的功能進行擴展。程序員

Python的C語言接口很適合封裝C語言實現的各類函數,若是要封裝C++的類,使用boost_python或者SWIG更方便和合適。python爬蟲

1 模塊封裝

假設咱們有一個C函數:python2.7

/* 文件名: mylib.c */
int addone(int a) {
    return a+1;
}

若是想在Python解釋器中調用該函數,則應該首先將其實現爲Python中的一個模塊,這須要編寫相應的封裝接口,以下所示:函數

/* wrap_mylib.c */
#include <Python.h>
#include "mylib.h"
PyObject* wrap_addone(PyObject* self, PyObject* args) 
{
  int n, result;

  if (! PyArg_ParseTuple(args, "i:fact", &n))
    return NULL;
  result = addone(n); /*這裏調用C函數 */
  return Py_BuildValue("i", result);
}
static PyMethodDef mylibMethods[] = 
{
  {"addone", wrap_addone, METH_VARARGS, "Add one to N"},
  {NULL, NULL}
};
void initmylib() 
{
  PyObject* m;
  m = Py_InitModule("mylib", mylibMethods);
}

上面就是一個典型的Python擴展模塊,它至少應該包含三個部分:ui

  1. 導出函數:wrap_addone();
  2. 方法列表:mylibMethods[];
  3. 初始化函數: initmylib()

2 導出函數

要在Python解釋器中調用C語言中的某個函數,首先要爲它編寫對應的導出函數,上述例子中的導出函數爲wrap_addone。在Python的C語言擴展中,全部的導出函數都具備相同的函數原型:spa

PyObject* wrap_method(PyObject* self, PyObject* args);

這個函數是Python解釋器和C函數進行交互的接口,通常以wrap_開頭後面跟上C語言的函數名,這樣命名把導出函數和C語言函數對應起來使得代碼更加清晰。它帶有兩個參數:self和args。指針

參數self 只在C函數被實現爲內聯方法(built-in method)時才被用到,一般該參數的值爲空(NULL)。
參數args 中包含了Python解釋器要傳遞給C函數的全部參數,一般使用Python的C語言擴展接口提供的函數PyArg_ParseTuple()來得到這些參數值。code

全部的導出函數都返回一個PyObject指針,若是對應的C函數沒有真正的返回值(即返回值類型爲void),則應返回一個全局的None對象(Py_None),並將其引用計數增1,以下所示:

PyObject* wrap_method(PyObject *self, PyObject *args) 
{
  Py_INCREF(Py_None);
  return Py_None;
}

3 方法列表

方法列表中列出了全部能夠被Python解釋器使用的方法,上述例子對應的方法列表爲:

static PyMethodDef mylibMethods[] = 
{
  {"addone", wrap_addone, METH_VARARGS, "Add one to N"},
  {NULL, NULL}
};

方法列表中的每項由四個部分組成:

  • 方法名
  • 導出函數
  • 參數傳遞方式
  • 方法描述

方法名是從Python解釋器中調用該方法時所使用的名字。
參數傳遞方式則規定了Python向C函數傳遞參數的具體形式,可選的兩種方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是參數傳遞的標準形式,它經過Python的元組在Python解釋器和C函數之間傳遞參數,若採用METH_KEYWORD方式,則Python解釋器和C函數之間將經過Python的字典類型在二者之間進行參數傳遞。

4 初始化函數

全部的Python擴展模塊都必需要有一個初始化函數,以便Python解釋器可以對模塊進行正確的初始化。Python解釋器規定全部的初始化函數的函數名都必須以init開頭,並加上模塊的名字。對於模塊mylib來講,則相應的初始化函數爲:

void initmylib() 
{
  PyObject* m;
  m = Py_InitModule("mylib", mylibMethods);
}

當Python解釋器須要導入該模塊時,將根據該模塊的名稱查找相應的初始化函數,一旦找到則調用該函數進行相應的初始化工做,初始化函數則經過調用Python的C語言擴展接口所提供的函數Py_InitModule(),來向Python解釋器註冊該模塊中全部能夠用到的方法。

5 編譯連接

要在Python解釋器中使用C語言編寫的擴展模塊,必須將其編譯成動態連接庫的形式。下面以Linux爲例,介紹如何將C編寫的Python擴展模塊編譯成動態連接庫:

$ gcc -fpic -shared -o mylib.so \
             -I/usr/include/python2.7 \
            mylib.c wrap_mylib.c

6 在Python中調用

上面編譯生成的Python擴展模塊的動態連接庫,能夠在Python中直接import。以下所示:

veelion@gtx:~$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.addone(7)
8
>>>

這裏生成的.so動態庫和上一篇中不用Python的C語言生成的動態庫是不同的,從生成過程和使用方法就能夠看出來,這裏的動態庫使用起來感受就是一個Python模塊,直接import就能夠了。

相關文章
相關標籤/搜索