请选择 进入手机版 | 继续访问电脑版

python调用go或c语言

[复制链接]
卓小兔 发表于 2021-1-2 19:46:03 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
  本文档由小小明个人学习整理
  文章链接:https://blog.csdn.net/as604049322/article/details/112058313
  pdf下载所在:https://download.csdn.net/download/as604049322/13999212
python调用go语言

​ Python是一个生产力很高的语言,可以大概以最高的效率完成最多的事,但是Python的性能,是我们一直诟病的一个问题,尤其是一个大锁GIL。固然现在大部分步调都是(IO)网络麋集型步调,Python足以胜任,但是如果说我们已经存在的项目大概想要开发的项目中,存在有盘算麋集型的步调场景,该如何提升性能呢?
​ 一般是可以用C\C++重写Python盘算麋集的地方,来提高性能,但是C\C++是有一些学习本钱的,指针和自己释放内存都有一定门槛。Go就很方便了,自动垃圾自动接纳,另有天生高并发等优势。
​ python的ctypes模块提供了和C语言兼容的数据范例和函数来加载so/dll动态链接库文件,而GO语言自己就可以编译出符合c语言规范的dll或so动态链接库,基于这两项特性,于是我们可以顺利的使用python来调用go语言。
Golang情况设置

Go官方镜像站点:https://golang.google.cn/dl/
选择默认的最高版本就好,Go代码向下兼容版本之间的差别并无所谓
检察是否安装乐成
  1. >go versiongo version go1.15.2 windows/amd64
复制代码
注:由于已经是1.11+版本,我们以后使用go mod举行管理依赖,不需要设置GOPATH等希奇的东西。
设置GOPROXY(署理)
大概我们需要借用Go下载一些包什么的,但是默认官网源GOPROXY=https://proxy.golang.org,direct,在国内访问不到
输入go env检察Go设置:
  1. >go env...set GOPROXY=https://proxy.golang.org,directset GOROOT=D:\Go...
复制代码
改成国内镜像站点:
  1. go env -w GOPROXY=https://goproxy.cn,direct
复制代码
再次检察Go设置:
  1. >go env...set GOPROXY=https://goproxy.cn,directset GOROOT=D:\Go...
复制代码
go语言跨平台编译

跨平台编译,也叫交织编译,我可以在win平台上,编译成linux平台可执行的文件。
这也是Go备受青睐的原因,像java,python,php等语言,我们开发一般是在win平台上开发,摆设的时候在linux上摆设,在处置惩罚第三方依赖是比力贫苦,不光开发累,运维也累,虽然现在有docker办理了这个痛点,但是应该照旧没原生来的舒服。
如果使用Go的话,不管是什么第三方依赖,最终只会打包成一个可执行文件,直接摆设立刻,而且是高并发方式,心再大一点,连Nginx都不消,但是一点不消担心并发问题。
示例
Windows下编译linux平台可执行步调:
cmd下依次执行以下下令:
  1. SET CGO_ENABLED=0  // 禁用CGOSET GOOS=linux  // 目标平台是linuxSET GOARCH=amd64  // 目标处置惩罚器架构是amd64
复制代码
然后执行go build,得到的就是可以大概在linux上的可执行文件。
将这个文件上传到linux服务器上,纵然Go情况都没有,都可以执行乐成。
Windows下编译Mac平台64位可执行步调:
  1. SET CGO_ENABLED=0SET GOOS=darwinSET GOARCH=amd64go build
复制代码
Mac 下编译 Linux 和 Windows平台 64位 可执行步调:
  1. CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go buildCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
复制代码
Linux 下编译 Mac 和 Windows 平台64位可执行步调:
  1. CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go buildCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
复制代码
python与go性能对比

为了更好的体现出来优化之后的效果,我们大概对比一下两个语言在盘算麋集情况下的差距。
测试:分别盘算一个亿(100000000)的累加模拟大量盘算。
Python代码:
  1. import timedef run(n):    sum = 0    for i in range(n):        sum += i    print(sum)if __name__ == '__main__':    startTime = time.time()    run(100000000)    endTime = time.time()    print("耗时:", endTime - startTime)
复制代码
耗时5s左右:

Go代码:
  1. package mainimport (  "fmt"  "time")func run(n int) {  sum := 0  for i := 0; i < n; i++ {    sum += i  }  fmt.Println(sum)}func main() {  var startTime = time.Now()  run(100000000)  fmt.Println("耗时:", time.Since(startTime))}
复制代码
耗时50ms左右:

Go代码编译为Python可调用的.so文件

安装64位gcc工具MinGW
去https://sourceforge.net/projects/mingw-w64/下载后,一步步安装
已经将离线包上传到了百度云:
https://pan.baidu.com/s/1ZmjQUf5QcBbeHCi7mIrYxg 提取码: edc5
Windows适应于x86_64-8.1.0-release-win32-seh-rt_v6-rev0,直接解压并将MinGW的bin目次参加情况变量中后即可使用。
检察gcc版本:
  1. >gcc -vUsing built-in specs.COLLECT_GCC=gccCOLLECT_LTO_WRAPPER=D:/develop/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exeTarget: x86_64-w64-mingw32Configured with: ../../../src/gcc-8.1.0/configure --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --prefix=/mingw64 --with-sysroot=/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64 --enable-shared --enable-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdcxx-time=yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-libstdcxx-pch --disable-libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=nocona --with-tune=core2 --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-isl=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-pkgversion=&#39;x86_64-win32-seh-rev0, Built by MinGW-W64 project&#39; --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS=&#39;-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include&#39; CXXFLAGS=&#39;-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include&#39; CPPFLAGS=&#39; -I/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include&#39; LDFLAGS=&#39;-pipe -fno-ident -L/c/mingw810/x86_64-810-win32-seh-rt_v6-rev0/mingw64/opt/lib -L/c/mingw810/prerequisites/x86_64-zlib-static/lib -L/c/mingw810/prerequisites/x86_64-w64-mingw32-static/lib &#39;Thread model: win32gcc version 8.1.0 (x86_64-win32-seh-rev0, Built by MinGW-W64 project)
复制代码
需要被编译.so文件的go代码有些要求,比方必须导入C:
  1. package mainimport (  "C" //C必须导入)//export runfunc run(n int) int{  // 必须通过export 函数名格式的注释申明该函数可以被外部接口  sum := 0  for i := 0; i < n; i++ {    sum += i  }  fmt.Println("我是Go代码,我跑完了,我的效果是:",sum)  return sum}func main() {  //main函数中什么都不要写,和包名main要对应}
复制代码
编译为.so文件供Python调用:
  1. go build -buildmode=c-shared -o s1.so s1.go
复制代码
格式:go build -buildmode=c-shared -o 输出的.so文件 go源文件
会生成.h文件和.so文件,.so文件供Python调用,如下图所示:

Ptyhon调用so文件

将上述生成的.so文件复制到Python项目标同一级目次。
编写s1.py,依然是盘算一个亿,关键部分由Go生成的.so执行:
  1. from ctypes import *import timeif __name__ == &#39;__main__&#39;:    startTime = time.time()    s = CDLL("s1.so")  # 加载s1.so文件    result = s.run(100000000)  # 调用Go生成的.so文件内里的run函数    print("result:", result)    endTime = time.time()    print("耗时:", endTime - startTime)
复制代码
共耗时:0.04s左右:

可以看到,虽然速度很快,但是Python在调用Go生成的.so文件之后,拿到的返回值竟然是错的,但是在Go中打印简直实对的!
但是盘算一些的比力小的数,以10023为例,效果是正确的:

.h文件探究

上面的问题是因为默认返回值范例存储范围有限导致的,下面将详细分析go编译生成的c中间文件一探毕竟。
打开.h文件,翻到末端:

找到extern开头的声明:
  1. extern GoInt run(GoInt n);
复制代码
这是前面go源码中声明的run方法被转换为c语言代码,体现参数和返回值范例在c语言中都是GoInt范例。
翻到范例界说的位置:

可以看到,GoInt实在就是GoInt64,GoInt64的范例是long long范例。
Python使用ctypes模块调用.so文件时有一个对应表:
参考:https://docs.python.org/zh-tw/3.7/library/ctypes.html
ctypes 范例C 范例Python 范例c_bool_Boolbool (1)c_charchar单字符字节对象c_wcharwchar_t单字符字符串c_bytecharintc_ubyteunsigned charintc_shortshortintc_ushortunsigned shortintc_intintintc_uintunsigned intintc_longlongintc_ulongunsigned longintc_longlong__int64 或 long longintc_ulonglongunsigned __int64 或 unsigned long longintc_size_tsize_tintc_ssize_tssize_t 或 Py_ssize_tintc_floatfloatfloatc_doubledoublefloatc_longdoublelong doublefloatc_char_pchar * (以 NUL 末端)字节串对象或 Nonec_wchar_pwchar_t * (以 NUL 末端)字符串或 Nonec_void_pvoid *int 或 None根据上述表格可以发现,在C中的long long范例对应的ctype范例是c_longlong,在python中的范例是int。
python的默认数值处置惩罚范例是Long(8字节),go语言编译的run方法,未申明的情况下返回值范例却是Int(4字节),所以当盘算效果高出Int的可存储范围时就会出现问题。
Int的取值范围为:-2^31 — 2^31-1,即-2147483648 — 2147483647
现在根据实际的ctype在python中申明run的实际返回值范例即可:
  1. from ctypes import *import timeif __name__ == &#39;__main__&#39;:    beginTime = time.time()    s = CDLL("s1.so")  # 加载s1.so文件    # 根据查表,C中的long long,对应的ctypes 是 c_longlong    s.run.restype = c_longlong  # 声明.so的run函数返回值范例,固定格式    result = s.run(100000000)  # 调用Go生成的.so文件内里的run函数    print(result)    endTime = time.time()    print("耗时:", endTime - beginTime)
复制代码

现在效果就没有问题了。
处置惩罚返回值为字符串的情况

s2.go的代码:
  1. package mainimport (        "C" //C必须导入)//export speakfunc speak(n int) string {        return "996好累呀,难过休息一天,好好休息 "}func main() {        //main函数中什么都不要写,和包名main要对应}
复制代码
检察s2.h:
  1. typedef struct { const char *p; ptrdiff_t n; } _GoString_;typedef _GoString_ GoString;...extern GoString speak(GoInt n);...
复制代码
上面体现GoString是_GoString_范例,而_GoString_是char *和ptrdiff_t的布局体
在c语言规范中,ptrdiff_t是C/C++标准库中界说的一个与呆板相关的数据范例。ptrdiff_t范例变量通常用来生存两个指针减法利用的效果。ptrdiff_t界说在stddef.h(cstddef)这个文件内。ptrdiff_t通常被界说为long int范例,可以被界说为long long范例。
查表可知,在python中应申明c_char_p和c_longlong的布局体:
  1. class GoString(Structure):    # typedef struct { const char *p; ptrdiff_t n; } _GoString_;    # ptrdiff_t == long long    _fields_ = [("p", c_char_p), ("n", c_longlong)]
复制代码
s3.py完整代码:
  1. from ctypes import *import timeclass GoString(Structure):    # typedef struct { const char *p; ptrdiff_t n; } _GoString_;    _fields_ = [("p", c_char_p), ("n", c_longlong)]if __name__ == &#39;__main__&#39;:    beginTime = time.time()    s = CDLL("s2.so")  # 加载s1.so文件    s.speak.restype = GoString    speakStr = s.speak(5)    # 返回的是字节范例,需要转字符串,返回的内容在.p中,.n是切的长度    speakStr = speakStr.p[:speakStr.n].decode("utf-8")    print("speak:", speakStr)    endTime = time.time()    print("耗时:", endTime - beginTime)
复制代码

但是上面的这种代码只支持返回的字符串为常量,一旦我将go代码修改为以下内容再重复以上步调时:
s2.go代码:
  1. package mainimport (        "C" //C必须导入        "strconv")//export speakfunc speak(n int) string {        s := "996好累呀,难过休息一天,好好休息 " + strconv.Itoa(n)        return s}func main() {        //main函数中什么都不要写,和包名main要对应}
复制代码
重复以上步调,运行s3.py,出现如下错误:

这是因为此时的string不是c语言的对象,而是go语言的对象,修改为如下代码即可:
s2.go代码:
  1. package mainimport (        "C" //C必须导入        "strconv")//export speakfunc speak(n int) *C.char {        s := "996好累呀,难过休息一天,好好休息 " + strconv.Itoa(n)        return C.CString(s)}func main() {        //main函数中什么都不要写,和包名main要对应}
复制代码
以上代码申明返回c语言的字符串范例,检察.h文件可以看到:
  1. extern char* speak(GoInt n);
复制代码
那么s3.py代码只需修改为:
  1. from ctypes import *import timeif __name__ == &#39;__main__&#39;:    beginTime = time.time()    s = CDLL("s3.so")  # 加载s1.so文件    s.speak.restype = c_char_p    speakStr = s.speak(7).decode("utf-8")    print("speak:", speakStr)    endTime = time.time()    print("耗时:", endTime - beginTime)
复制代码

顺利运行。
使用ctypes访问C代码

根本示例

实现两数求和的C代码add.c文件:
  1. #include int add_int(int, int);float add_float(float, float);int add_int(int num1, int num2){    return num1 + num2;}float add_float(float num1, float num2){    return num1 + num2;}
复制代码
将C文件编译为.so文件:
  1. #For Linux or windowsgcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c#For Macgcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
复制代码
在Python代码中来调用它:
  1. from ctypes import *adder = CDLL(&#39;adder.so&#39;)res_int = adder.add_int(4, 5)print("Sum of 4 and 5 = " + str(res_int))add_float = adder.add_floatadd_float.restype = c_floata = c_float(5.5)b = c_float(4.1)print("Sum of 5.5 and 4.1 = ", str(add_float(a, b)))
复制代码
输出:
  1. Sum of 4 and 5 = 9Sum of 5.5 and 4.1 =  9.600000381469727
复制代码
ctypes接口允许我们在调用C函数时参数使用原生Python中默认的字符串型和整型,而对于其他类似布尔型和浮点型这样的范例,必须要使用正确的ctype范例才可以。如向adder.add_float()函数传参时, 要先将Python中的float范例转化为c_float范例,然后才华传送给C函数。
复杂示例

编码c代码,sample.c文件的内容为:
  1. #include int gcd(int x, int y) {    int g = y;    while (x > 0) {        g = x;        x = y % x;        y = g;    }    return g;}int in_mandel(double x0, double y0, int n) {    double x = 0, y = 0, xtemp;    while (n > 0) {        xtemp = x * x - y * y + x0;        y = 2 * x * y + y0;        x = xtemp;        n -= 1;        if (x * x + y * y > 4) return 0;    }    return 1;}int divide(int a, int b, int *remainder) {    int quot = a / b;    *remainder = a % b;    return quot;}double avg(double *a, int n) {    int i;    double total = 0.0;    for (i = 0; i < n; i++) {        total += a[i];    }    return total / n;}typedef struct Point {    double x, y;} Point;double distance(Point *p1, Point *p2) {    return hypot(p1->x - p2->x, p1->y - p2->y);}
复制代码
下令行执行以下代码,编译c:
  1. gcc -shared -o sample.so sample.c
复制代码
在 sample.so 所在文件相同的目次编写python代码,sample.py文件
  1. import ctypes_mod = ctypes.cdll.LoadLibrary(&#39;sample.so&#39;)# int gcd(int, int)gcd = _mod.gcdgcd.argtypes = (ctypes.c_int, ctypes.c_int)gcd.restype = ctypes.c_int# int in_mandel(double, double, int)in_mandel = _mod.in_mandelin_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)in_mandel.restype = ctypes.c_int# int divide(int, int, int *)_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_intdef divide(x, y):    rem = ctypes.c_int()    quot = _divide(x, y, rem)    return quot, rem.value# void avg(double *a, int n)# 界说 &#39;double *&#39;参数的范例class DoubleArrayType:    def from_param(self, param):        typename = type(param).__name__        if hasattr(self, &#39;from_&#39; + typename):            return getattr(self, &#39;from_&#39; + typename)(param)        elif isinstance(param, ctypes.Array):            return param        else:            raise TypeError("Can&#39;t convert %s" % typename)    # Cast from array.array objects    def from_array(self, param):        if param.typecode != &#39;d&#39;:            raise TypeError(&#39;must be an array of doubles&#39;)        ptr, _ = param.buffer_info()        return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))    # Cast from lists/tuples    def from_list(self, param):        val = ((ctypes.c_double) * len(param))(*param)        return val    from_tuple = from_list    # Cast from a numpy array    def from_ndarray(self, param):        return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))_avg = _mod.avg_avg.argtypes = (DoubleArrayType(), ctypes.c_int)_avg.restype = ctypes.c_doubledef avg(values):    return _avg(values, len(values))# struct Point { }class Point(ctypes.Structure):    _fields_ = [(&#39;x&#39;, ctypes.c_double),                (&#39;y&#39;, ctypes.c_double)]# double distance(Point *, Point *)distance = _mod.distancedistance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))distance.restype = ctypes.c_double
复制代码
然后就可以加载并使用内里界说的C函数了,编写test.py
  1. import sampleprint("sample.gcd(35, 42):", sample.gcd(35, 42))print("sample.in_mandel(0, 0, 500):", sample.in_mandel(0, 0, 500))print("sample.in_mandel(2.0, 1.0, 500):", sample.in_mandel(2.0, 1.0, 500))print("sample.divide(42, 8):", sample.divide(42, 8))print("sample.avg([1, 2, 3]):", sample.avg([1, 2, 3]))p1 = sample.Point(1, 2)p2 = sample.Point(4, 5)print("sample.distance(p1, p2):", sample.avg([1, 2, 3]))
复制代码
执行效果:
  1. sample.gcd(35, 42): 7sample.in_mandel(0, 0, 500): 1sample.in_mandel(2.0, 1.0, 500): 0sample.divide(42, 8): (5, 2)sample.avg([1, 2, 3]): 2.0sample.distance(p1, p2): 2.0
复制代码
复杂示例分析

加载c函数库

如果C函数库被安装为一个标准库,那么可以使用 ctypes.util.find_library() 函数来查找它所在的位置:
  1. >>> from ctypes.util import find_library>>> find_library(&#39;m&#39;)&#39;libm.so.6&#39;>>> find_library(&#39;pthread&#39;)&#39;libpthread.so.0&#39;>>> find_library(&#39;sample&#39;)
复制代码
如果是非标准库,则需要知道C函数库的位置,然后使用 ctypes.cdll.LoadLibrary() 来加载它:
  1. _mod = ctypes.cdll.LoadLibrary(_path) #_path是C函数库的位置,全路径和相对路径都可以
复制代码
指定参数和返回值的范例

函数库被加载后,需要提取特定的符号指定它们的范例。比方:
  1. # int in_mandel(double, double, int)in_mandel = _mod.in_mandelin_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)in_mandel.restype = ctypes.c_int
复制代码
这段代码中,函数的.argtypes 属性是一个元组,包罗了某个函数的输入参数,而 .restype 是函数的返回范例。
ctypes 界说的c_double, c_int, c_short, c_float等代表了对应的C数据范例。
为了让Python可以大概通报正确的参数范例而且正确的转换数据,这些范例签名的绑定是很重要的一步。如果省略这个范例签名的步调,大概导致代码不能正常运行,甚至整个表明器历程挂掉。
指针参数需要以ctypes对象形式传入

原生的C代码的范例有时跟Python不能明确的对应上来,比方:
  1. # c代码中的int divide(int, int, int *)_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_int# python代码中的调用x = 0divide(10, 3, x)
复制代码
这种写法违反了Python对于整数的不可更改原则,而且大概会导致整个表明器陷入一个黑洞中。
对于涉及到指针的参数,通常需要先构建一个相应的ctypes对象再作为参数传入:
  1. x = ctypes.c_int()divide(10, 3, x)
复制代码
ctypes.c_int 实例是作为指针被传进去的,跟寻常Python整数差别的是,c_int 对象是可以被修改的。
.value属性可被用来获取或更改这个值:
  1. x.value
复制代码
对于这种不像Python的C调用,通常可以写一个包装函数:
  1. # int divide(int, int, int *)_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_intdef divide(x, y):    rem = ctypes.c_int()    quot = _divide(x, y, rem)    return quot, rem.value
复制代码
参数包罗数组

对于avg()函数,double avg(double *a, int n),C代码渴望担当到一个double范例的数组指针和一个数组的长度值。
在Python中数组有多种形式,包罗列表、元组、array 模块的数组、 numpy 数组等。
DoubleArrayType 演示了怎样处置惩罚这种情况。
方法 from_param() 担当一个单个参数然后将其向下转换为一个符合的ctypes对象:
  1. def from_param(self, param):    typename = type(param).__name__    if hasattr(self, &#39;from_&#39; + typename):        return getattr(self, &#39;from_&#39; + typename)(param)    elif isinstance(param, ctypes.Array):        return param    else:        raise TypeError("Can&#39;t convert %s" % typename)
复制代码
参数的范例名被提取出来并被用于分发到一个更详细的方法中去。
比方,如果参数是一个列表,那么 typename 就是 list ,然后 from_list 方法就会被调用。
  1. def from_list(self, param):    val = ((ctypes.c_double) * len(param))(*param)    return val
复制代码
演示通过交互式下令行将list列表转换为 ctypes 数组:
  1. >>> import ctypes>>> nums = [1, 2, 3]>>> a = (ctypes.c_double * len(nums))(*nums)>>> a>>> a[0]1.0>>> a[1]2.0>>> a[2]3.0
复制代码
如果参数是一个numpy数组,那么 typename 就是 ndarray,然后 from_ndarray方法就会被调用:
  1. def from_ndarray(self, param):        return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
复制代码
如果参数是一个数组对象,那么 typename 就是 array,然后 from_array方法就会被调用:
  1. def from_array(self, param):    if param.typecode != &#39;d&#39;:        raise TypeError(&#39;must be an array of doubles&#39;)    ptr, _ = param.buffer_info()    return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
复制代码
对于数组对象,buffer_info()方法可以获取到数组对应的内存所在和长度,ctypes.cast()可以将内存所在转换为ctypes 指针对象:
  1. >>> import array>>> a = array.array(&#39;d&#39;,[1,2,3])>>> aarray(&#39;d&#39;, [1.0, 2.0, 3.0])>>> ptr,length = a.buffer_info()>>> ptr4298687200>>> length3>>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
复制代码
通过界说 DoubleArrayType类并在 avg() 范例签名中使用它,那么这个函数就能担当多个差别的类数组输入了:
  1. import samplesample.avg([1,2,3])2.0sample.avg((1,2,3))2.0import arraysample.avg(array.array(&#39;d&#39;,[1,2,3]))2.0import numpysample.avg(numpy.array([1.0,2.0,3.0]))2.0
复制代码
参数包罗布局体

对于布局体,只需要简单的界说一个类,包罗相应的字段和范例即可:
  1. class Point(ctypes.Structure):    _fields_ = [(&#39;x&#39;, ctypes.c_double),                (&#39;y&#39;, ctypes.c_double)]
复制代码
范例签名绑定只需:
  1. # double distance(Point *, Point *)distance = _mod.distancedistance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))distance.restype = ctypes.c_double
复制代码
一旦类被界说后,就可以在范例签名中大概是需要实例化布局体的代码中使用它。比方:
  1. >>> p1 = sample.Point(1,2)>>> p2 = sample.Point(4,5)>>> p1.x1.0>>> p1.y2.0>>> sample.distance(p1,p2)4.242640687119285
复制代码
将函数指针转换为可调用对象

获取C函数的内存所在(经测试,在linux上支持,windows上不支持):
  1. import ctypeslib = ctypes.cdll.LoadLibrary(None)# 获取C语言math库的sin()函数的所在addr = ctypes.cast(lib.sin, ctypes.c_void_p).valueprint(addr)
复制代码
上述代码在linux下得到整数140266666308000,而在Windows下会报错TypeError: LoadLibrary() argument 1 must be str, not None
有了函数的内存所在,就可以将它转换成一个Python可调用对象:
  1. # 将函数所在转换成一个Python的可调用对象,参数为函数的返回值范例和参数范例functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)sin = functype(addr)print(sin)
复制代码
CFUNCTYPE() 的第一个参数是返回范例,接下来的参数是参数范例,生成的对象被当做寻常的可通过 ctypes 访问的函数来使用。
打印:
调用测试:
  1. >>> import math>>> math.pi3.141592653589793>>> sin(math.pi)1.2246467991473532e-16>>> sin(math.pi/2)1.0>>> sin(math.pi/6)0.49999999999999994>>> sin(2)0.9092974268256817>>> sin(0)0.0
复制代码
这内里涉及的技能被广泛使用于各种高级代码生成技能,比如即时编译,在LLVM函数库中可以看到。
下面简单演示下 llvmpy 扩展,构建一个小的聚集函数,获取它的函数指针,然后转换为一个Python可调用对象,并执行函数:
  1. >>> from llvm.core import Module, Function, Type, Builder>>> mod = Module.new(&#39;example&#39;)>>> f = Function.new(mod,Type.function(Type.double(), [Type.double(), Type.double()], False), &#39;foo&#39;)>>> block = f.append_basic_block(&#39;entry&#39;)>>> builder = Builder.new(block)>>> x2 = builder.fmul(f.args[0],f.args[0])>>> y2 = builder.fmul(f.args[1],f.args[1])>>> r = builder.fadd(x2,y2)>>> builder.ret(r)>>> from llvm.ee import ExecutionEngine>>> engine = ExecutionEngine.new(mod)>>> ptr = engine.get_pointer_to_function(f)>>> ptr4325863440>>> foo = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double, ctypes.c_double)(ptr)>>> foo(2,3)13.0>>> foo(4,5)41.0>>> foo(1,2)5.0
复制代码
注意:这是在直接跟呆板级别的内存所在和当地呆板码打交道,而不是Python函数。
处置惩罚参数包罗字符串的情况

测试步调str1.c
  1. #include void print_chars(char *s) {    printf("%s\n",s);    while (*s) {        printf("%2x ", (unsigned char) *s);        s++;    }    printf("\n");}int main() {    print_chars("Hello");}
复制代码
执行效果:
  1. > gcc str1.c&a.exeHello48 65 6c 6c 6f
复制代码
编译c步调为so文件:
  1. gcc -shared -o str1.so str1.c
复制代码
用python调用:
  1. import ctypes_mod = ctypes.cdll.LoadLibrary(&#39;str1.so&#39;)# void print_chars(char *s)print_chars = _mod.print_charsprint_chars.argtypes = (ctypes.c_char_p,)print_chars(b&#39;Hello&#39;)print_chars(b&#39;Hello\x00World&#39;)
复制代码
打印效果:
  1. Hello48 65 6c 6c 6f Hello48 65 6c 6c 6f
复制代码
不能直接传入python的字符串范例,比方:print_chars(&#39;Hello World&#39;)
否则会报错:ctypes.ArgumentError: argument 1: : wrong type
如果需要通报字符串而不是字节,可以先编码成 UTF-8 转成字节:
  1. >>> print_chars(&#39;Hello World&#39;.encode(&#39;utf-8&#39;))Hello World48 65 6c 6c 6f 20 57 6f 72 6c 64
复制代码
来源:https://blog.csdn.net/as604049322/article/details/112058313
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )