在機(jī)器學(xué)習(xí)中,很多時(shí)候我們需要Python和C的混合編程,最重要的原因是為了性能效率的提升: 解釋型語言一般比編譯型語言慢,一般提高性能的有效做法是,先做性能測試,找出性能瓶頸部分,然后把瓶頸部分在擴(kuò)展中實(shí)現(xiàn)。
本文的目標(biāo)是在windows平臺(tái)下(使用pycharm),實(shí)現(xiàn)python調(diào)用C語言編寫的程序。主要參考資料:
上面兩篇博客已經(jīng)寫得很詳細(xì),但是都是基于linux平臺(tái)和mac,我這里算是作為一篇windows平臺(tái)的補(bǔ)充和總結(jié),還有自己踩的一些坑,跟大家分享。
要使用python使用c語言編寫的程序,大致分成兩種方法,一種是純手寫,一種是用第三方的接口工具。本文將分成兩部分分別講述。
一.純手寫調(diào)用c語言
1.編寫和調(diào)試c語言程序
<p>
在windows下編寫c語言面臨一個(gè)選擇編譯器的問題,不像linux一樣可以直接選用gcc。這里我推薦使用VisualStudio2008作為c語言程序開發(fā)的IDE。如果你一開始就選擇了vs2008,將在后期會(huì)省去很多工作。這是因?yàn)閜ython2.7在windows下的編譯器就是使用vs2008的工具。當(dāng)然如果你用別的版本的vs,后面也有解決方法。還有些同學(xué)選擇使用gcc在windows下的版本,也就是minGccForWin。但是不推薦這種方法,據(jù)說這在后期會(huì)有無數(shù)莫名其妙的問題。
<p>ok,假設(shè)你安裝了vs的任何一個(gè)版本,我們編寫以下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;
}
<p>其中reverse函數(shù)實(shí)現(xiàn)的是字符串翻轉(zhuǎn)的功能,加入main函數(shù)是為了單元測試。
2.利用樣板來包裝代碼
<p>第一步調(diào)試完程序以后,要進(jìn)行代碼包裝。
- 包含python頭文件
#include "Python.h"
- 為每一個(gè)函數(shù)增加一個(gè)型如PyObject* Module_func()的包裝函數(shù)
static PyObject *Extest_reverse(PyObject *self, PyObject *args) {
char *orignal;
//s表示需要傳遞進(jìn)來的參數(shù)類型為字符串,如果是,就賦值給original,如果不是,返回NULL;
if (!(PyArg_ParseTuple(args, "s", &orignal))) {
//包裝函數(shù)返回NULL,就會(huì)在Python調(diào)用中產(chǎn)生一個(gè)TypeError的異常
return NULL;
}
//需要把c中計(jì)算的結(jié)果轉(zhuǎn)成python對象,s代表字符串對象類型。
return (PyObject *)Py_BuildValue("s", reverse(orignal));
}
<p>最重要的兩個(gè)個(gè)方法:
1.PyArg_ParseTuple(args, "s", &orignal)
將python格式的參數(shù)按照指定格式解析,轉(zhuǎn)存。
2.y_BuildValue("s", reverse(orignal))
將c格式的結(jié)果按照指定格式轉(zhuǎn)換成python格式。
<p>下面是python和c對應(yīng)的類型轉(zhuǎn)換參數(shù)表:

Py_BuildValue的用法表:

注:上面兩張圖來自python擴(kuò)展實(shí)現(xiàn)方法--python與c混和編程
<p>
- 為每個(gè)模塊增加一個(gè)型如PyMethodDef ModuleMethods[]的數(shù)組
static PyMethodDefExtestMethods[] = {
{"fac", Extest_fac, METH_VARARGS},
{"doppel", Extest_doppel, METH_VARARGS},
{"reverse", Extest_reverse, METH_VARARGS},
{NULL, NULL},
};
<p>有了這個(gè)聲明,python就可以方便地找到方法了。METH_VARARGS代表參數(shù)以tuple的形式傳入。
- 增加模塊初始化函數(shù)void initMethod()
void initExtest() {
Py_InitModule("Extest", ExtestMethods);
}
最后加入在模塊被python導(dǎo)入時(shí)進(jìn)行調(diào)用的代碼。
<p>至此,包裝代碼的工作結(jié)束。把上面的代碼按順序組裝即可。
3.編譯與測試
編寫setup.py
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest.c'])])
激動(dòng)人心的時(shí)刻到了,開始編譯,輸入:
python setup.py build
但是,報(bào)錯(cuò)了,這是什么?
error: Unable to find vcvarsall.bat
還是編譯器出了問題。如果你沒有安裝VS2008,一般都會(huì)碰到這個(gè)問題。以下給出解決方法:
- 1.先去下載Microsoft Visual C++ Compiler for Python 2.7
- 2.安裝
再來試試。
python setup.py build
為什么還是報(bào)同樣的錯(cuò)誤??
- 3.手動(dòng)改寫注冊表
這里要考慮你的python是32位還是64位的。
打開regedit。添加項(xiàng):
32位:
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Setup\VC
64位:
HKEY_CURRENT_USER\Software\Wow6432Node\Microsoft\VisualStudio\9.0\Setup\VC
此項(xiàng)下新建字符串值: 名稱:productdir
數(shù)據(jù):vcvarsall.bat所在路徑
注意:路徑中不包含最后的反斜杠。
再來試試。
python setup.py build
好的,這次成功了。項(xiàng)目目錄中新增了一個(gè)build文件夾:

我們用的時(shí)候只需要Extest.pyd文件即可。其實(shí)本質(zhì)上就是.dll動(dòng)態(tài)鏈接庫。
<p>調(diào)用的程序:
#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文件會(huì)直接到當(dāng)前目錄,直接import即可。這種方法比較推薦!

<p>另一種方法是直接install。即
python seup.py install
這樣就可以直接import了。
4.性能測試
編寫性能測試的代碼如下:
#coding=utf-8
import Extest as extest
import time
def python_reverse(string):
return string[::-1]
start = time.time()
for i in range(100000):
extest.reverse('string hahahahahaha')
print u'使用c花費(fèi):'
print time.time()-start
start = time.time()
for j in range(100000):
python_reverse('string hahahahahaha')
print u'使用python花費(fèi):'
print time.time()-start
結(jié)果:

可以看到,用c還是比python快的。
<p>至此,手寫的方式介紹完畢。
二.使用Swig
<p>使用swig相對簡單,但是當(dāng)你習(xí)慣了手寫以后,相信手寫也是很方便的。當(dāng)然,不管你使用swig還是手寫,用windows的話,上面安裝vc編譯器還有修改注冊表的步驟都是繞不過去的。
1.下載、安裝swig
去官網(wǎng)下載。
參考官方文檔。
安裝完別忘了添加環(huán)境變量。
2.編寫、調(diào)試c語言程序
- example.h
/*File: example.h*/
int fact(int n);
- example.c
/* File: example.c */
//計(jì)算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);
}
}
3.配置swig,編譯
- example.i
/* File: example.i */
%module example
%{
#define SWIG_FILE_WITH_INIT
#include "example.h"
%}
int fact(int n);
配置文件聲明了模塊名稱,原c語言程序,以及方法。
在終端運(yùn)行:
swig -python example.i
如果編譯的是C++文件,需要加上-C++選項(xiàng):
swig -c++ -python example.i
運(yùn)行完這個(gè)命令后,在工作目錄里會(huì)出現(xiàn)example_wrap.c和example.py,但是現(xiàn)在這個(gè)模塊還不能直接調(diào)用,因?yàn)檫€缺少動(dòng)態(tài)鏈接庫。
需要編寫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
這時(shí)目錄里多了一個(gè).pyd文件,大功告成。
4.使用
