標籤:

Python C Extesion (pyd)

安裝環境:

  • win10 X64

  • Python 2.7.12 |Anaconda 4.2.0 (32-bit)
  • VS2015

demo:

源代碼主要有兩部分,一部分是cpp(h)文件,一部分是python文件。包括demo.cpp、setup.py、test_demo.py。由demo.cpp和setup.py文件編譯生成pyd文件,test_demo.py用來測試。

cpp代碼

#define EXPORT_MINDPY_DLLn#include "Python.h"n#include "ndarrayobject.h"n//python的頭文件和numpy的頭文件nnstatic PyObject* RaiseError(PyObject* self, PyObject* args)n{nt//一般的python函數返回值和傳入參數均為PyObject*。nt//傳入參數為self和args。ntPyErr_SetString(PyExc_ValueError, "Ooops");nt//拋出ValueError異常ntreturn NULL;n}nnstatic PyObject* GetNdarray(PyObject* self, PyObject* args)n{ntnpy_byte da[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };ntnpy_intp dim = 9;ntPyArrayObject * out = (PyArrayObject*)PyArray_NewFromDescr(&PyArray_Type, PyArray_DescrFromType(PyArray_BYTE), 1, &dim, NULL, NULL, NPY_C_CONTIGUOUS, NULL);nt//生成長度為9的一維矩陣ntmemcpy(out->data, da, sizeof(npy_byte) * 9);ntreturn PyArray_Return(out);n}nnnstatic PyMethodDef Methods[] =n{nt{ "raise_error", RaiseError, METH_VARARGS, "raise a simple error" },nt{ "getNdarray", GetNdarray, METH_VARARGS, "get ndarray"},nt{ NULL, NULL, 0, NULL }n};nnPyMODINIT_FUNCnninitdemo(void)n{nt//注意函數名格式init+模塊名nt(void)Py_InitModule("demo", Methods);nt// a important things is to call import_array!!!ntimport_array();n}n

C部分的代碼分三塊,首先是引用了"Python.h"和"ndarrayobject.h",這兩個頭文件分別是python的頭文件和numpy的頭文件。"ndarrayobject.h"的目錄為「Libsite-packagesnumpycoreincludenumpy」,可能需要添加到環境變數。

接下來是兩個函數,是比較常用的功能舉例,其中RaiseError函數的作用是拋出了一個異常,GetNdarray是生成了一個numpy的矩陣並傳入python域。PyObject其實是個掛羊頭賣狗肉的貨,看起來像個對象,實際上是個結構體。這個也是在寫C擴展的時候需要注意的一些問題,因為在c與py間傳遞的都是C風格的數據類型,類和對象什麼的只能通過傳遞一個結構體,再由python的解釋器根據結構體里的內容生成。

最後是一些打包python模塊必要的處理,包括PyMethodDef、PyMODINIT_FUNC、和initdemo。要注意的是initdemo的格式,init後面跟模塊名稱,如果用到numpy庫了還要調用一下import_array。

編譯(setup.py)

接下來是寫setup.py這個文件用來編譯生成pyd文件,setup.py格式如下:

#coding:utf-8nfrom distutils.core import setup, Extensionnimport numpynnnpinclude = numpy.get_include()#get numpy dirnnmodule = Extension(demo, sources=[demo.cpp],n include_dirs=[npinclude],n library_dirs=[],n libraries=[]n )nsetup(name= demo,n version= 1.0,n ext_modules = [module])n

在sources處包含demo.cpp就行了,需要注意的地方是用到了numpy需要將numpy的頭文件目錄包含進去。編譯命令如下:

activate Anaconda2 nSET VS90COMNTOOLS=%VS140COMNTOOLS%npython setup.py build_ext --inplacen

第一行的作用是激活虛擬環境,我的主環境是Anaconda3,裡面是個python3的環境,用conda裝py2的環境太慢太坑,換了國內伺服器裝的也慢,直接下了個Anaconda2裝到env文件夾下了,居然也能用activate命令激活環境,挺方便的。

第二行是設置編譯器為VS140也就是VS2015,py2自帶的是VS90。

第三行是用setup文件編譯,參數為build_ext,--inplace表示編譯到同一文件夾下。第三行是必須的,前兩行根據情況省略。

測試代碼

接下來是測試代碼,隨便寫的,將就著看吧。

import unittestnimport demonimport numpy as npnnclass TestStringMethods(unittest.TestCase):nn def test_ndarray(self):n self.assertTrue(hasattr(demo,getNdarray))n array = demo.getNdarray()n self.assertIsInstance(array, np.ndarray)n self.assertEquals(list(demo.getNdarray()), [1,2,3,4,5,6,7,8,9])n n def test_raise(self):n self.assertTrue(hasattr(demo,raise_error))n try:n demo.raise_error()n except ValueError as e:n self.assertIsInstance(e, ValueError)n assert Ooops in e.argsn except Exception as e:n raise ennif __name__ == __main__:n unittest.main()n

移植時的依賴

最後安利兩個神器,一個是depends.exe,另一個是vc_redist.x86.exe。為什麼要介紹這兩個東西呢,是因為依賴的動態庫沒設置好的話會在import時報錯。

如果報錯是「XXX不是有效的win32應用」,原因是引用的動態庫里有X64的動態庫。如果報錯是「ImportError: DLL load failed:找不到指定的模塊」,排除了代碼問題,那就是某個需要的動態庫沒添加進環境,這時候就需要用depends這個神器來查看引用了。depends可以查看exe文件和dll文件的依賴,pyd文件本質上就是個dll文件,同樣可以用depends查看。

由於是用VS編譯的,在打包或移植到其他的電腦上時會出現ImportError,這時候裝個VS往往是不現實的,可以簡單的裝個vc的開發包就行了,根據編譯時的版本選擇好即可。


推薦閱讀:

復盤:隨機漫步
Python 2.7安裝常用的科學計算分析包
numpy和pandas入門
python與numpy使用的一些小tips(2)

TAG:Python | numpy | CC |