《Python實戰-構建基於股票的量化交易系統》小冊子,雖然主要側重於 Python 實戰講解,但在內容設計上提供了前置基礎章節幫助讀者快速掌握基礎工具的使用,所以小冊適合的人羣僅具有Python最基礎編程經驗便可。python
同時咱們會持續更新一些關於Python和量化相關的基礎文章,幫助你們夯實基礎。接下來咱們介紹下Python中擴展C語言加快執行速度的實現方法。linux
當咱們提到一門編程語言的效率時,一般包含了開發效率和運行效率這兩層意思。Python做爲一門高級語言,它功能強大,易於掌握,可以快速的開發軟件,「life is short,we use python!」,想必這些優勢是毋庸置疑的,可是做爲一門解釋性語言,執行速度的侷限性致使在處理某些高頻任務時存在不足。算法
因爲Python自己由C語言實現的,開發性能要求較高的程序模塊能夠經過擴展運行效率更高的C語言來彌補自身的弱點。另外有些算法已經有開源的C庫,那麼也不必用Python重寫一份,只須要經過Python進行C庫的調用便可。編程
接下來經過實例介紹如何在Python 程序中整合既有的C語言模塊,從而充分發揮Python 語言和 C 語言各自的優點。數組
使用Python編寫一個遞歸函數和循環函數,應用Python的計時庫timeit測試函數執行10000次所須要的時間分別爲57ms和41ms。bash
實現代碼以下:app
from timeit import timeit
def factorial(n):
if n<2:return 1
return factorial(n-1)*n
def rooporial(n):
if n<2:return 1
ans = 1
for i in range(1,n+1):
ans *=i
return ans
if __name__ == '__main__':
print "factorial",factorial(20),timeit('factorial(20)','from __main__ import factorial',number=10000) #timeit(‘函數名’,‘運行環境’,number=運行次數)
print "rooporial",rooporial(20),timeit('rooporial(20)','from __main__ import rooporial',number=10000)
複製代碼
打印返回:python2.7
factorial 2432902008176640000 0.0578598976135
factorial 2432902008176640000 0.0410023010987
複製代碼
固然遞歸方法使程序的結構簡潔,但因爲它逐層深刻調用的機制使得執行效率不如循環,如下的測試能夠發現每一次遞歸是新一次的函數調用,會產生新的局部變量,增長了執行時間。可是即便使用For循環實現也須要41ms時間,接下來咱們嘗試更快的實現方式。編程語言
測試代碼以下:函數
def up_add_down(n):
print("level %d: n location %p\n",n,id(n))
if n<=4:up_add_down(n+1)
print("level %d: n location %p\n",n,id(n))
return
複製代碼
打印返回:
('level %d: n location %p\n', 0, 144136380)
('level %d: n location %p\n', 1, 144136368)
('level %d: n location %p\n', 2, 144136356)
('level %d: n location %p\n', 3, 144136344)
('level %d: n location %p\n', 4, 144136332)
('level %d: n location %p\n', 5, 144136320)
('level %d: n location %p\n', 5, 144136320)
('level %d: n location %p\n', 4, 144136332)
('level %d: n location %p\n', 3, 144136344)
('level %d: n location %p\n', 2, 144136356)
('level %d: n location %p\n', 1, 144136368)
('level %d: n location %p\n', 0, 144136380)
複製代碼
Python在設計之初就考慮到經過足夠抽象的機制讓C和C++之類的編譯型的語言導入到Python腳本代碼中,在Python的官方網站上也找到了擴展和嵌入Python解釋器對應的方法。這裏介紹下如何將C編寫的函數擴展至Python解釋器中。
將C編寫的遞歸函數存爲wrapper.c,做爲Python的擴展庫。同時須要對C函數增長一個型如PyObject* Module_func()
的封裝接口,該接口用於Python解釋器的交互。將封裝接口加入至型如PyMethodDef ModuleMethods[]
的數組中,Python解釋器可以從數組中導入並調用到封裝接口。最後是實現對擴展庫的初始化函數,調用Py_InitModule()
函數,把擴展庫和ModuleMethods[]
數組的名字傳遞進去,以便於解釋器能正確的調用庫中的函數。
wrapper.c實現代碼以下:
#include <Python.h>
unsigned long long factorial(int n)
{
if(n<2)return 1;
return factorial(n-1)*n;
}
PyObject* wrap_fact(PyObject* self,PyObject* args)
{
int n;
unsigned long long result;
if(!PyArg_ParseTuple(args,"i:fact",&n))return NULL;//i 整形
result = factorial(n);
return Py_BuildValue("L",result);//L longlong型
}
static PyMethodDef wrapperMethods[] =
{
{"fact",wrap_fact,METH_VARARGS,"Caculate N!"},//METH_NOARGS無需參數/METH_VARARGS須要參數;
{NULL,NULL},
};
int initwrapper()
{
PyObject* m;
m = Py_InitModule("wrapper",wrapperMethods);//參數:擴展庫名稱/庫所包含的方法
return 0;
}
複製代碼
安裝python-dev包含Python.h頭文件。安裝命令:sudo apt-get python-dev
在linux環境下wrapper.c編譯成動態連接庫wrapper.so
編譯命令:gcc wrapper.c -fPIC -shared -o wrapper.so -I/usr/include/python2.7
Python文件中import wrapper導入動態連接庫,在import語句導入庫時會執行初始化函數
Python文件中wrapper.fact()
方式對C函數調用時,封裝函數wrap_fact()
先會被調用,封裝函數接收到一個Python整形對象,PyArg_ParseTuple將Python整形對象轉爲C整形參數,而後調用C的factorial()
函數並將C整數參數傳入,通過運算後獲得一個C長整形的返回值,Py_BuildValue
把C長整形返回值轉爲Python的長整形對象做爲最終整個函數調用的結果。
timeit測試函數wrapper.fact(20)執行10000次所的時間只須要5.9ms。
factorial_rc 2432902008176640000 0.00598216056824
複製代碼
Python內建ctypes庫使用了各個平臺動態加載動態連接庫的方法,並在Python源生代碼基礎上經過類型映射方式將Python與二進制動態連接庫相關聯,實現Python與C語言的混合編程,能夠很方便地調用C語言動態連接庫中的函數。(ctypes源碼路徑:/Modules/_ctypes/_ctypes.c、/Modules/_ctypes/callproc.c)
將C編寫的遞歸函數存爲a.c,不須要對C函數通過Python接口封裝
#include <stdio.h>
#include <stdlib.h>
unsigned long long factorial(int n)
{
if(n<2)return 1;
return factorial(n-1)*n;
}
(2)在linux環境下a.c編譯成動態連接庫a.so
編譯命令:gcc a.c -fPIC -shared -o a.so
Python文件中調用動態連接庫a.so,在Windows平臺下,最終調用的是Windows API中LoadLibrary函數和GetProcAddress函數,在Linux和Mac OS X平臺下,最終調用的是Posix標準中的dlopen和dlsym函數。ctypes庫內部完成PyObject* 和C types之間的類型映射,使用時只需設置調用參數和返回值的轉換類型便可。
from ctypes import cdll
from ctypes import *
libb = cdll.LoadLibrary('./a.so')
def factorial_c(n):
return libb.factorial(n)
if __name__ == '__main__':
libb.factorial.restype=c_ulonglong#返回類型
libb.factorial.argtype=c_int#傳入類型
print "factorial_c",factorial_c(20),timeit('factorial_c(20)','from __main__ import factorial_c',number=10000)
(4)timeit測試函數libb.factorial(20)執行10000次所的時間須要8.5ms。
factorial_c 2432902008176640000 0.00857210159302
複製代碼
factorial、factorial_c、factorial_rc分別爲Python腳本、ctypes 庫和Python源生代碼擴展方式來實現函數的執行時間。經過執行時間對比能夠發現調用C函數來擴展Python功能能夠大大提升執行速度,而Python自帶的ctypes庫因爲在源生代碼上進行封裝,執行時間會高於源生代碼擴展方式,但ctypes庫使用方便,特別適合應用在第三方封裝代碼,提供動態連接庫和調用文檔的場合。
關於完整代碼能夠加入小冊交流羣獲取。更多的量化交易內容歡迎你們訂閱小冊閱讀!!