用Python使用C語言程式(Windows平臺)
前言 在機器學習中,很多時候我們需要Python和C的混合程式設計,最重要的原因是為了效能效率的提升: 解釋型語言一般比編譯型語言慢,一般提高效能的有效做法是,先做效能測試,找出效能瓶頸部分,然後把瓶頸部分在擴充套件中實現。
本文的目標是在windows平臺下(使用pycharm),實現python呼叫C語言編寫的程式。主要參考資料:
python擴充套件實現方法--python與c混和程式設計(http://www.cnblogs.com/btchenguang/archive/2012/09/04/2670849.html)
混合程式設計:用 C 語言來擴充套件 Python 大法吧!(http://www.jianshu.com/p/09994c9d8489)
上面兩篇部落格已經寫得很詳細,但是都是基於linux平臺和mac,我這裡算是作為一篇windows平臺的補充和總結,還有自己踩的一些坑,跟大家分享。
要使用python使用c語言編寫的程式,大致分成兩種方法,一種是純手寫,一種是用第三方的介面工具。本文將分成兩部分分別講述。
01
純手寫呼叫c語言
1、編寫和除錯C語言程式
在windows下編寫c語言面臨一個選擇編譯器的問題,不像linux一樣可以直接選用gcc。這裡我推薦使用VisualStudio2008作為c語言程式開發的IDE。如果你一開始就選擇了vs2008,將在後期會省去很多工作。
這是因為python2.7在windows下的編譯器就是使用vs2008的工具。當然如果你用別的版本的vs,後面也有解決方法。還有些同學選擇使用gcc在windows下的版本,也就是minGccForWin。但是不推薦這種方法,據說這在後期會有無數莫名其妙的問題。
ok,假設你安裝了vs的任何一個版本,我們編寫以下c語言程式:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Python.h" #define BUFSIZE 10 char *reverse(char *s) { register char t; char *p = s; char *q = (s + (strlen(s) - 1)); while (p < q) { t = *p; *p++ = *q; *q-- = t; } return s; } int main() { char s[BUFSIZE]; strcpy(s, "abcdef"); printf("reversing 'abcdef', we get '%s'n", reverse(s)); strcpy(s, "madam"); printf("reversing 'madam', we get '%s'n", reverse(s)); return 0; }
其中reverse函式實現的是字串翻轉的功能,加入main函式是為了單元測試。
2、利用樣板來包裝程式碼
第一步除錯完程式以後,要進行程式碼包裝。
包含python標頭檔案
#include "Python.h"
為每一個函式增加一個型如PyObject* Module_func()的包裝函式
static PyObject *Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
//s表示需要傳遞進來的引數型別為字串,如果是,就賦值給original,如果不是,返回NULL;
if (!(PyArg_ParseTuple(args, "s", &orignal)))
{
//包裝函式返回NULL,就會在Python呼叫中產生一個TypeError的異常 return NULL;
}
//需要把c中計算的結果轉成python物件,s代表字串物件型別。
return (PyObject *)Py_BuildValue("s", reverse(orignal)); }
最重要的兩個個方法:
1.PyArg_ParseTuple(args, "s", &orignal)
將python格式的引數按照指定格式解析,轉存。
2.y_BuildValue("s", reverse(orignal))
將c格式的結果按照指定格式轉換成python格式。
下面是python和c對應的型別轉換引數表:
引數轉換.png
Py_BuildValue的用法表:
Py_BuildValue的用法表.png
注:上面兩張圖來自python擴充套件實現方法--python與c混和程式設計(http://www.cnblogs.com/btchenguang/archive/2012/09/04/2670849.html)
為每個模組增加一個型如PyMethodDef ModuleMethods[]的陣列
static PyMethodDefExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL}
, };
有了這個宣告,python就可以方便地找到方法了。METH_VARARGS代表引數以tuple的形式傳入。
增加模組初始化函式void initMethod()
void initExtest() { Py_InitModule("Extest", ExtestMethods); }
最後加入在模組被python匯入時進行呼叫的程式碼。
至此,包裝程式碼的工作結束。把上面的程式碼按順序組裝即可。
3、編譯與測試
編寫setup.py
from distutils.core import setup, Extension MOD = 'Extest' setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest.c'])])
激動人心的時刻到了,開始編譯,輸入:
python setup.py build
但是,報錯了,這是什麼?
error: Unable to find vcvarsall.bat
還是編譯器出了問題。如果你沒有安裝VS2008,一般都會碰到這個問題。以下給出解決方法:
1、先去下載Microsoft Visual C++ Compiler for Python 2.7(https://www.microsoft.com/en-us/download/details.aspx?id=44266)
2、 安裝
再來試試。
python setup.py build
為什麼還是報同樣的錯誤??
3、手動改寫登錄檔
這裡要考慮你的python是32位還是64位的。
開啟regedit。新增項:
32位: HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio9.0SetupVC 64位: HKEY_CURRENT_USERSoftwareWow6432NodeMicrosoftVisualStudio9.0SetupVC
此項下新建字串值: 名稱:productdir 資料:vcvarsall.bat所在路徑 注意:路徑中不包含最後的反斜槓。 再來試試。
python setup.py build
好的,這次成功了。專案目錄中新增了一個build資料夾:
build.jpg
我們用的時候只需要Extest.pyd檔案即可。其實本質上就是.dll動態連結庫。
呼叫的程式:
#coding=utf-8 import os import sys sys.path.append(os.getcwd() +"/build/lib.win32-2.7/") import Extest as extes print extest.reverse('hello')
或者像這樣:
python setup.py build_ext --inplace
這樣,pyd檔案會直接到當前目錄,直接import即可。這種方法比較推薦!
目錄.jpg
另一種方法是直接install。即
python seup.py install
這樣就可以直接import了。
4、效能測試
編寫效能測試的程式碼如下:
#coding=utf-8
import Extest as extes
timport timedef python_reverse(string):
return string[::-1]
start = time.time()
for i in range(100000):
extest.reverse('string hahahahahaha')
print u'使用c花費:'
print time.time()-start
start = time.time()
for j in range(100000):
python_reverse('string hahahahahaha')
print u'使用python花費:
'print time.time()-start
結果:
測試結果.jpg
可以看到,用c還是比python快的。至此,手寫的方式介紹完畢。
02
使用Swig
使用swig相對簡單,但是當你習慣了手寫以後,相信手寫也是很方便的。當然,不管你使用swig還是手寫,用windows的話,上面安裝vc編譯器還有修改登錄檔的步驟都是繞不過去的。
1、下載、安裝swig
去官網下載。 參考官方文件。 安裝完別忘了新增環境變數。
2、編寫、除錯C語言程式
example.h
/*File: example.h*/ int fact(int n);
example.c
/* File: example.c */
//計算n!
#include "example.h" int fact(int n) {
if (n < 0){
/* This should probably return an error, but this is simpler */
return 0; }
else if (n == 0) {
return 1;
} else {
/* testing for overflow would be a good idea here */
return n * fact(n-1); } }
03
配置swig,編譯
example.i
/* File: example.i */ %module example %{ #define SWIG_FILE_WITH_INIT #include "example.h" %} int fact(int n);
配置檔案聲明瞭模組名稱,原c語言程式,以及方法。
在終端執行:
swig -python example.i
如果編譯的是C++檔案,需要加上-C++選項:
swig -c++ -python example.i
執行完這個命令後,在工作目錄裡會出現example_wrap.c和example.py,但是現在這個模組還不能直接呼叫,因為還缺少動態連結庫。
需要編寫setup.py如下:
""" setup.py file for SWIG example"""from distutils.core import setup, Extension example_module = Extension('_example', sources=['example_wrap.c', 'example.c'], ) setup(name = 'example', version = '0.1', author = "SWIG Docs", description = """Simple swig example from docs""", ext_modules = [example_module], py_modules = ["example"], )
在終端裡輸入:
python setup.py build_ext --inplace
這時目錄裡多了一個.pyd檔案,大功告成。
04