溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Python 與 C/C++ 交互的幾種方式

發(fā)布時(shí)間:2020-06-25 20:57:42 來(lái)源:網(wǎng)絡(luò) 閱讀:1153 作者:吳金瑞 欄目:編程語(yǔ)言

python作為一門(mén)腳本語(yǔ)言,其好處是語(yǔ)法簡(jiǎn)單,很多東西都已經(jīng)封裝好了,直接拿過(guò)來(lái)用就行,所以實(shí)現(xiàn)同樣一個(gè)功能,用Python寫(xiě)要比用C/C++代碼量會(huì)少得多。但是優(yōu)點(diǎn)也必然也伴隨著缺點(diǎn)(這是肯定的,不然還要其他語(yǔ)言干嘛),python最被人詬病的一個(gè)地方可能就是其運(yùn)行速度了。這這是大部分腳本語(yǔ)言共同面對(duì)的問(wèn)題,因?yàn)闆](méi)有編譯過(guò)程,直接逐行執(zhí)行,所以要慢了一大截。所以在一些對(duì)速度要求很高的場(chǎng)合,一般都是使用C/C++這種編譯型語(yǔ)言來(lái)寫(xiě)。但是很多時(shí)候,我們既想使用python的簡(jiǎn)介優(yōu)美,又不想損失太多的性能,這個(gè)時(shí)候有沒(méi)有辦法將python與C/C++結(jié)合到一起呢?這樣在性能與速度要求不高的地方,可以用pyhton寫(xiě),而關(guān)鍵的運(yùn)算部分用C/C++寫(xiě),這樣就太好了。python在做科學(xué)計(jì)算或者數(shù)據(jù)分析時(shí),這是一個(gè)非常普遍的需求。要想實(shí)現(xiàn)這個(gè)功能,python為我們提供了不止一種解決辦法。下面我就逐一給大家介紹。

  一、Cython 混合python與C

  官方網(wǎng)址:http://docs.cython.org/en/latest/src/quickstart/overview.html。首先來(lái)看看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.

簡(jiǎn)單來(lái)說(shuō),cython就是一個(gè)內(nèi)置了c數(shù)據(jù)類(lèi)型的python,它是一個(gè)python的超集,兼容幾乎所有的純python代碼,但是又可以使用c的數(shù)據(jù)類(lèi)型。這樣就可以同時(shí)使用c庫(kù),又不失python的優(yōu)雅。

好了,不講太多廢話,直接來(lái)看cython如何使用吧。這里的介紹大部分來(lái)自官網(wǎng),由于cython涉及到的東西還比較多,所以這里只是簡(jiǎn)單的入門(mén)介紹,詳細(xì)的信息請(qǐng)移步英文官網(wǎng)。

使用cython有兩種方式:第一個(gè)是編譯生成Python擴(kuò)展文件(有點(diǎn)類(lèi)似于dll,即動(dòng)態(tài)鏈接庫(kù)),可以直接import使用。第二個(gè)是使用jupyter notebook或sage notebook 內(nèi)聯(lián) cython代碼。

先看第一種。還是舉最經(jīng)典的hello world的例子吧。新建一個(gè)hello.pyx文件,定義一個(gè)hello函數(shù)如下:

def hello(name):    print("Hello %s." % name)

然后,我們來(lái)寫(xiě)一個(gè)setup.py 文件(寫(xiě)python擴(kuò)展幾乎都要寫(xiě)setup.py文件,我之前也簡(jiǎn)單介紹過(guò)怎么寫(xiě))如下:

Python 與 C/C++ 交互的幾種方式

 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time   : 2017/5/8 9:09 4 # @Author : Lyrichu 5 # @Email  : 919987476@qq.com 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 # 編寫(xiě)setup函數(shù)14 setup(15     name = "Hello",16     ext_modules = cythonize("hello.pyx")17 )

Python 與 C/C++ 交互的幾種方式

其中 ext_modules 里面寫(xiě)你要 編譯的.pyx文件名字。OK,所有工作都完成了。接下來(lái),進(jìn)入cmd,切換到setup.py 所在的文件,然后執(zhí)行命令: python setup.py build_ext --inplace 就會(huì)編譯生成一個(gè)build 文件夾以及一個(gè).pyd文件了,這個(gè)pyd文件就是python的動(dòng)態(tài)擴(kuò)展庫(kù),--inplace 的意思是在當(dāng)前文件目錄下生成.pyd文件,不加這一句就會(huì)在build文件夾中生成。截圖如下:

Python 與 C/C++ 交互的幾種方式

圖 1

可以看出,除了生成了一個(gè)pyd文件之外,還生成了一個(gè).c文件。test.py是我們用來(lái)測(cè)試的文件,在里面寫(xiě)如下內(nèi)容:

from hello import hello
hello("lyric")

從hello 模塊導(dǎo)入 hello函數(shù),然后直接調(diào)用就可以了。結(jié)果輸出 Hello lyric.

再來(lái)看如何 在 jupyter notebook中使用cython。如果你裝過(guò)ipython,一個(gè)升級(jí)版的python交互式環(huán)境,你應(yīng)該聽(tīng)過(guò) ipyhton notebook的大名,現(xiàn)在它升級(jí)了,改名叫jupyter notebook 了。簡(jiǎn)單來(lái)說(shuō),這個(gè)就是一個(gè)可以在網(wǎng)頁(yè)環(huán)境下交互式使用python的工具,不僅可以實(shí)時(shí)看到計(jì)算結(jié)果,還可以直接展示表格,圖片等,功能還是非常強(qiáng)大的。首先你得安裝jupyter notebook.我印象中安裝了ipython之后應(yīng)該就會(huì)帶了jupyter了。如果沒(méi)有,可以直接 pip install jupyter .然后輸入命令 jupyter notebook 就會(huì)在瀏覽器中打開(kāi)jupyter了。如下圖2 所示:

Python 與 C/C++ 交互的幾種方式

圖 2

點(diǎn)擊右上角的new按鈕,可以選擇新建一個(gè)文本文件或者文件夾,markdown或者python文件,這里我們選擇新建一個(gè)pyhton 文件,然后就會(huì)轉(zhuǎn)到一個(gè)新的窗口了,如下圖3:

Python 與 C/C++ 交互的幾種方式

圖 3

In[]:和ipython一樣,就代表著我們要輸入代碼的地方,輸入代碼之后,點(diǎn)擊向右的三角形符號(hào),就會(huì)執(zhí)行代碼了。

首先輸入 %load_ext cython ,然后執(zhí)行,%開(kāi)頭的語(yǔ)句是jupyter的魔法命令,%是行命令,%%是單元命令,具體不多說(shuō),有空給大家專(zhuān)門(mén)介紹一下notebook的使用。

接下來(lái)輸入:

1 %%cython2 cdef int a = 03 for i in range(10):4     a += i5 print(a)

%%cython 表明將cython內(nèi)嵌到j(luò)upyter,cdef 是cython的關(guān)鍵字,用于定義c類(lèi)型,這里將a定義為c中的int類(lèi)型,并且初始化為0.

然后后面的循環(huán)就是累加0到9的意思,最后輸出45.

另外,我們?nèi)绻敕治龃a 的執(zhí)行情況,可以輸入 %%cython --annotate 命令,這樣就可以輸出結(jié)果的同時(shí),也輸出 詳細(xì)的代碼執(zhí)行情況報(bào)告了。截圖如圖4 所示:

Python 與 C/C++ 交互的幾種方式

圖 4

jupyter notebook 可以?xún)?nèi)嵌cython,不用我們手寫(xiě)setup.py 文件,省去了編譯的過(guò)程,方便了cython的使用,所以不是正式做項(xiàng)目,只是寫(xiě)一寫(xiě)小東西用jupyter+cython還是非常方便的。

前面提到了 cdef,再舉一個(gè)稍微復(fù)雜點(diǎn)的例子吧。還是引用官網(wǎng)的例子,寫(xiě)一個(gè)算積分的函數(shù).新建 integrate.pyx 文件,寫(xiě)入如下內(nèi)容:

Python 與 C/C++ 交互的幾種方式

#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time   : 2017/5/8 9:26# @Author : Lyrichu# @Email  : 919987476@qq.com# @File   : integrate.py'''@Description: 積分運(yùn)算,使用 cython cdef 關(guān)鍵字'''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 # 返回定積分

Python 與 C/C++ 交互的幾種方式

這段代碼應(yīng)該也是比較好理解的,f()函數(shù)是被積函數(shù),a,b是積分的上下限,N是分割小矩形的個(gè)數(shù),注意這里將 變量i,s,dx全部都用cdef 聲明為c類(lèi)型了,一般來(lái)說(shuō),在需要密集計(jì)算的地方比如循環(huán)或者復(fù)雜運(yùn)算,可以將對(duì)應(yīng)的變量聲明為c類(lèi)型,可以加快運(yùn)行速度。

然后和上面一樣編寫(xiě) setup.py ,就是把 pyx的文件名改一下,代碼我就不貼了。然后python setup.py build_ext --inplace 執(zhí)行。得到pyd文件,編寫(xiě)測(cè)試文件test.py如下:

Python 與 C/C++ 交互的幾種方式

 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time   : 2017/5/8 9:35 4 # @Author : Lyrichu 5 # @Email  : 919987476@qq.com 6 # @File   : test.py 7 ''' 8 @Description: 測(cè)試使用cython 混合c與python的integrate 函數(shù)與純python寫(xiě)的integrate函數(shù)速度上的差異 9 '''10 from integrate import integrate_f11 import time12 13 a = 1 # 積分區(qū)間下界14 b = 2 # 積分區(qū)間上界15 N = 10000 # 劃分區(qū)間個(gè)數(shù)16 17 # 使用純python代碼寫(xiě)的integrate函數(shù)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 版本計(jì)算耗時(shí):%.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 版本計(jì)算耗時(shí):%.8f" % (end_time2 - start_time2))

Python 與 C/C++ 交互的幾種方式

上面的代碼,我們重新使用python寫(xiě)了一個(gè)積分函數(shù)py_integrate_f,與pyd中的integrate_f 函數(shù)進(jìn)行運(yùn)算對(duì)比,結(jié)果如下(圖5):

Python 與 C/C++ 交互的幾種方式

圖5

可以看出,使用了cython的版本比純Python的版本大概快了4、5倍的樣子,而這僅僅是將幾個(gè)變量改為c類(lèi)型的結(jié)果,可見(jiàn),cython確實(shí)可以方便地對(duì)python與c進(jìn)行混合,獲得速度上的提升,又不失去Python的簡(jiǎn)潔優(yōu)美。

最后再來(lái)說(shuō)下cython 如何調(diào)用c libraries. C 語(yǔ)言 stdlib 庫(kù)有一個(gè) atoi函數(shù),可以將字符串轉(zhuǎn)化為整數(shù),math庫(kù)有一個(gè)sin函數(shù),我們就以這兩個(gè)函數(shù)為例。新建 calling_c.pyx 文件,文件內(nèi)容如下:

Python 與 C/C++ 交互的幾種方式

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)

Python 與 C/C++ 交互的幾種方式

前兩行導(dǎo)入了C語(yǔ)言中的函數(shù),然后我們自定義了兩個(gè)函數(shù),parse_char_to_int 可以將字符串轉(zhuǎn)換為整數(shù),f_sin_squared 計(jì)算 x平方的sin函數(shù)值。寫(xiě) setup.py 文件,和之前差不多,但是要注意的是,在unix系統(tǒng)下,math庫(kù)默認(rèn)是不鏈接的,所以需要指明其位置,那么在unix系統(tǒng)下,setup.py 文件的內(nèi)容就需要增加Extension 一項(xiàng),如下:

Python 與 C/C++ 交互的幾種方式

from distutils.core import setupfrom distutils.extension import Extensionfrom Cython.Build import cythonize

ext_modules=[
    Extension("calling_c",
              sources=["calling_c.pyx"],
              libraries=["m"] # Unix-like specific    )
]

setup(
  name = "Calling_c",
  ext_modules = cythonize(ext_modules)
)

Python 與 C/C++ 交互的幾種方式

然后直接編即可。test.py文件如下:

Python 與 C/C++ 交互的幾種方式

 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time   : 2017/5/8 12:21 4 # @Author : Lyrichu 5 # @Email  : 919987476@qq.com 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 與 C/C++ 交互的幾種方式

需要注意的是,Python字符串不能直接傳入 parse_char_to_int 函數(shù),需要將其轉(zhuǎn)換為 bytes 類(lèi)型再傳入。運(yùn)行結(jié)果為:

n = 12
sin(pi/2)=1.000000

如果不想通過(guò)libc導(dǎo)入c語(yǔ)言模塊,cython也允許我們自己聲明c函數(shù)原型來(lái)導(dǎo)入,一個(gè)例子如下:

# 自己聲明c函數(shù)原型cdef extern from "math.h":
    cpdef double cos(double x)def f_cos(double x):    return cos(x)

使用了 extern 關(guān)鍵字。

每次都編寫(xiě)setup.py 文件,然后編譯,略顯麻煩。cython還提供了一種更簡(jiǎn)單的方法:pyximport。通過(guò)導(dǎo)入pyximport(安裝cython時(shí)會(huì)自動(dòng)安裝),在沒(méi)有引入額外的c庫(kù)的情況下,可以直接調(diào)用pyx中的函數(shù),更為直接與方便。以前面的hello 模塊為例,編寫(xiě)好hello.py文件之后,編寫(xiě)一個(gè)pyximport_test.py 文件,文件內(nèi)容如下:

import pyximport
pyximport.install()import hello
hello.hello("lyric")

直接運(yùn)行就會(huì)發(fā)現(xiàn),確實(shí)可以正確導(dǎo)入hello模塊。

cython的更多內(nèi)容,請(qǐng)大家自行訪問(wèn)官網(wǎng)查看。

其他python與c/c++ 混合編程的方式主要還有 使用 ctypes,cffi模塊以及swig。本來(lái)想一起寫(xiě)的,想想還是分開(kāi)寫(xiě)吧,不然太長(zhǎng)了。后續(xù)會(huì)陸續(xù)更新,敬請(qǐng)關(guān)注。


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI