標籤:htm 訪問 官方 gif img pes 例子 ogr 語言
python作為一門指令碼語言,其好處是文法簡單,很多東西都已經封裝好了,直接拿過來用就行,所以實現同樣一個功能,用Python寫要比用C/C++代碼量會少得多。但是優點也必然也伴隨著缺點(這是肯定的,不然還要其他語言幹嘛),python最被人詬病的一個地方可能就是其運行速度了。這這是大部分指令碼語言共同面對的問題,因為沒有編譯過程,直接逐行執行,所以要慢了一大截。所以在一些對速度要求很高的場合,一般都是使用C/C++這種編譯型語言來寫。但是很多時候,我們既想使用python的簡介優美,又不想損失太多的效能,這個時候有沒有辦法將python與C/C++結合到一起呢?這樣在效能與速度要求不高的地方,可以用pyhton寫,而關鍵的運算部分用C/C++寫,這樣就太好了。python在做科學計算或者資料分析時,這是一個非常普遍的需求。要想實現這個功能,python為我們提供了不止一種解決辦法。下面我就逐一給大家介紹。
一、Cython 混合python與C
官方網址:http://docs.cython.org/en/latest/src/quickstart/overview.html。首先來看看cython的官方介紹吧。
[Cython] is a programming language that makes writing C extensions for the Python language as easy as Python itself. It aims to become a superset of the [Python]language which gives it high-level, object-oriented, functional, and dynamic programming. Its main feature on top of these is support for optional static type declarations as part of the language. The source code gets translated into optimized C/C++ code and compiled as Python extension modules. This allows for both very fast program execution and tight integration with external C libraries, while keeping up the high programmer productivity for which the Python language is well known.
簡單來說,cython就是一個內建了c資料類型的python,它是一個python的超集,相容幾乎所有的純python代碼,但是又可以使用c的資料類型。這樣就可以同時使用c庫,又不失python的優雅。
好了,不講太多廢話,直接來看cython如何使用吧。這裡的介紹大部分來自官網,由於cython涉及到的東西還比較多,所以這裡只是簡單的入門介紹,詳細的資訊請移步英文官網。
使用cython有兩種方式:第一個是編譯產生Python擴充檔案(有點類似於dll,即動態連結程式庫),可以直接import使用。第二個是使用jupyter notebook或sage notebook 內聯 cython代碼。
先看第一種。還是舉最經典的hello world的例子吧。建立一個hello.pyx檔案,定義一個hello函數如下:
def hello(name): print("Hello %s." % name)
然後,我們來寫一個setup.py 檔案(寫python擴充幾乎都要寫setup.py檔案,我之前也簡單介紹過怎麼寫)如下:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/5/8 9:09 4 # @Author : Lyrichu 5 # @Email : [email protected] 6 # @File : setup.py 7 ‘‘‘ 8 @Description: setup.py for hello.pyx 9 ‘‘‘10 from Cython.Build import cythonize11 from distutils.core import setup12 13 # 編寫setup函數14 setup(15 name = "Hello",16 ext_modules = cythonize("hello.pyx")17 )
其中 ext_modules 裡面寫你要 編譯的.pyx檔案名稱字。OK,所有工作都完成了。接下來,進入cmd,切換到setup.py 所在的檔案,然後執行命令: python setup.py build_ext --inplace 就會編譯產生一個build 檔案夾以及一個.pyd檔案了,這個pyd檔案就是python的動態擴充庫,--inplace 的意思是在當前檔案目錄下產生.pyd檔案,不加這一句就會在build檔案夾中產生。如下:
圖 1
可以看出,除了產生了一個pyd檔案之外,還產生了一個.c檔案。test.py是我們用來測試的檔案,在裡面寫如下內容:
from hello import hellohello("lyric")
從hello 模組匯入 hello函數,然後直接調用就可以了。結果輸出 Hello lyric.
再來看如何 在 jupyter notebook中使用cython。如果你裝過ipython,一個升級版的python互動式環境,你應該聽過 ipyhton notebook的大名,現在它升級了,改名叫jupyter notebook 了。簡單來說,這個就是一個可以在網頁環境下互動式使用python的工具,不僅可以即時看到計算結果,還可以直接展示表格,圖片等,功能還是非常強大的。首先你得安裝jupyter notebook.我印象中安裝了ipython之後應該就會帶了jupyter了。如果沒有,可以直接 pip install jupyter .然後輸入命令 jupyter notebook 就會在瀏覽器中開啟jupyter了。如2 所示:
圖 2
點擊右上方的new按鈕,可以選擇建立一個文字檔或者檔案夾,markdown或者python檔案,這裡我們選擇建立一個pyhton 檔案,然後就會轉到一個新的視窗了,如3:
圖 3
In[]:和ipython一樣,就代表著我們要輸入代碼的地方,輸入代碼之後,點擊向右的三角形符號,就會執行代碼了。
首先輸入 %load_ext cython ,然後執行,%開頭的語句是jupyter的魔法命令,%是行命令,%%是單元命令,具體不多說,有空給大家專門介紹一下notebook的使用。
接下來輸入:
1 %%cython2 cdef int a = 03 for i in range(10):4 a += i5 print(a)
%%cython 表明將cython內嵌到jupyter,cdef 是cython的關鍵字,用於定義c類型,這裡將a定義為c中的int類型,並且初始化為0.
然後後面的迴圈就是累加0到9的意思,最後輸出45.
另外,我們如果想分析代碼 的執行情況,可以輸入 %%cython --annotate 命令,這樣就可以輸出結果的同時,也輸出 詳細的代碼執行情況報告了。4 所示:
圖 4
jupyter notebook 可以內嵌cython,不用我們手寫setup.py 檔案,省去了編譯的過程,方便了cython的使用,所以不是正式做項目,只是寫一寫小東西用jupyter+cython還是非常方便的。
前面提到了 cdef,再舉一個稍微複雜點的例子吧。還是引用官網的例子,寫一個算積分的函數.建立 integrate.pyx 檔案,寫入如下內容:
#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : 2017/5/8 9:26# @Author : Lyrichu# @Email : [email protected]# @File : integrate.py‘‘‘@Description: 積分運算,使用 cython cdef 關鍵字‘‘‘def f(double x): return x**2 - xdef integrate_f(double a,double b,int N): cdef int i cdef double s,dx s = 0 dx = (b-a)/N for i in range(N): s += f(a + i*dx)*dx return s # 返回定積分
這段代碼應該也是比較好理解的,f()函數是被積函數,a,b是積分的上下限,N是分割小矩形的個數,注意這裡將 變數i,s,dx全部都用cdef 聲明為c類型了,一般來說,在需要密集計算的地方比如迴圈或者複雜運算,可以將對應的變數聲明為c類型,可以加快運行速度。
然後和上面一樣編寫 setup.py ,就是把 pyx的檔案名稱改一下,代碼我就不貼了。然後python setup.py build_ext --inplace 執行。得到pyd檔案,編寫測試檔案test.py如下:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/5/8 9:35 4 # @Author : Lyrichu 5 # @Email : [email protected] 6 # @File : test.py 7 ‘‘‘ 8 @Description: 測試使用cython 混合c與python的integrate 函數與純python寫的integrate函數速度上的差異 9 ‘‘‘10 from integrate import integrate_f11 import time12 13 a = 1 # 積分區間下界14 b = 2 # 積分區間上界15 N = 10000 # 劃分區間個數16 17 # 使用純python代碼寫的integrate函數18 def py_f(x):19 return x**2 - x20 21 def py_integrate_f(a,b,N):22 dx = (b-a)/N23 s = 024 for i in range(N):25 s += py_f(a + i*dx)*dx26 return s27 28 start_time1 = time.time()29 integrate_f_res = integrate_f(a,b,N)30 print("integrate_f_res = %s" % integrate_f_res)31 end_time1 = time.time()32 print(u"cython 版本計算耗時:%.8f" % (end_time1 - start_time1))33 34 start_time2 = time.time()35 py_integrate_f_res = py_integrate_f(a,b,N)36 print("py_integrate_f_res = %s" % py_integrate_f_res)37 end_time2 = time.time()38 print(u"python 版本計算耗時:%.8f" % (end_time2 - start_time2))
上面的代碼,我們重新使用python寫了一個積分函數py_integrate_f,與pyd中的integrate_f 函數進行運算對比,結果如下(圖5):
圖5
可以看出,使用了cython的版本比純Python的版本大概快了4、5倍的樣子,而這僅僅是將幾個變數改為c類型的結果,可見,cython確實可以方便地對python與c進行混合,獲得速度上的提升,又不失去Python的簡潔優美。
最後再來說下cython 如何調用c libraries. C 語言 stdlib 庫有一個 atoi函數,可以將字串轉化為整數,math庫有一個sin函數,我們就以這兩個函數為例。建立 calling_c.pyx 檔案,檔案內容如下:
from libc.stdlib cimport atoifrom libc.math cimport sindef parse_char_to_int(char * s): assert s is not NULL,"byte string value is NULL" return atoi(s)def f_sin_squared(double x): return sin(x*x)
前兩行匯入了C語言中的函數,然後我們自訂了兩個函數,parse_char_to_int 可以將字串轉換為整數,f_sin_squared 計算 x平方的sin函數值。寫 setup.py 檔案,和之前差不多,但是要注意的是,在unix系統下,math庫預設是不連結的,所以需要指明其位置,那麼在unix系統下,setup.py 檔案的內容就需要增加Extension 一項,如下:
from distutils.core import setupfrom distutils.extension import Extensionfrom Cython.Build import cythonizeext_modules=[ Extension("calling_c", sources=["calling_c.pyx"], libraries=["m"] # Unix-like specific )]setup( name = "Calling_c", ext_modules = cythonize(ext_modules))
然後直接編即可。test.py檔案如下:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/5/8 12:21 4 # @Author : Lyrichu 5 # @Email : [email protected] 6 # @File : test.py 7 ‘‘‘ 8 @Description: test file 9 ‘‘‘10 from calling_c import f_sin_squared,parse_char_to_int11 str = "012"12 str_b = bytes(str,encoding=‘utf-8‘)13 n = parse_char_to_int(str_b)14 print("n = %d" %n)15 from math import pi,sqrt16 x = sqrt(pi/2)17 res = f_sin_squared(x)18 print("sin(pi/2)=%f" % res)
需要注意的是,Python字串不能直接傳入 parse_char_to_int 函數,需要將其轉換為 bytes 類型再傳入。運行結果為:
n = 12
sin(pi/2)=1.000000
如果不想通過libc匯入c語言模組,cython也允許我們自己聲明c函數原型來匯入,一個例子如下:
# 自己聲明c函數原型cdef extern from "math.h": cpdef double cos(double x)def f_cos(double x): return cos(x)
使用了 extern 關鍵字。
每次都編寫setup.py 檔案,然後編譯,略顯麻煩。cython還提供了一種更簡單的方法:pyximport。通過匯入pyximport(安裝cython時會自動安裝),在沒有引入額外的c庫的情況下,可以直接調用pyx中的函數,更為直接與方便。以前面的hello 模組為例,編寫好hello.py檔案之後,編寫一個pyximport_test.py 檔案,檔案內容如下:
import pyximportpyximport.install()import hellohello.hello("lyric")
直接運行就會發現,確實可以正確匯入hello模組。
cython的更多內容,請大家自行訪問官網查看。
其他python與c/c++ 混合編程的方式主要還有 使用 ctypes,cffi模組以及swig。本來想一起寫的,想想還是分開寫吧,不然太長了。後續會陸續更新,敬請關注。
Python 與 C/C++ 互動的幾種方式