本文档由小小明个人学习整理

文章链接: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代码向下兼容版本之间的差异并无所谓

查看是否安装成功

>go version
go version go1.15.2 windows/amd64

注:由于已经是1.11+版本,我们以后使用go mod进行管理依赖,不需要配置GOPATH等奇怪的东西。

配置GOPROXY(代理)

可能我们需要借用Go下载一些包什么的,但是默认官网源GOPROXY=https://proxy.golang.org,direct,在国内访问不到

输入go env查看Go配置:

>go env
...
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=D:\Go
...

改成国内镜像站点:

go env -w GOPROXY=https://goproxy.cn,direct

再次查看Go配置:

>go env
...
set GOPROXY=https://goproxy.cn,direct
set GOROOT=D:\Go
...

go语言跨平台编译

跨平台编译,也叫交叉编译,我可以在win平台上,编译成linux平台可执行的文件。

这也是Go备受青睐的原因,像java,python,php等语言,我们开发一般是在win平台上开发,部署的时候在linux上部署,在处理第三方依赖是比较麻烦,不仅开发累,运维也累,虽然现在有docker解决了这个痛点,但是应该还是没原生来的舒服。

如果使用Go的话,不管是什么第三方依赖,最终只会打包成一个可执行文件,直接部署即刻,并且是高并发方式,心再大一点,连Nginx都不用,但是一点不用担心并发问题。

示例

Windows下编译linux平台可执行程序:

cmd下依次执行以下命令:

SET CGO_ENABLED=0  // 禁用CGO
SET GOOS=linux  // 目标平台是linux
SET GOARCH=amd64  // 目标处理器架构是amd64

然后执行go build,得到的就是能够在linux上的可执行文件。

将这个文件上传到linux服务器上,即使Go环境都没有,都可以执行成功。

Windows下编译Mac平台64位可执行程序:

SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build

Mac 下编译 Linux 和 Windows平台 64位 可执行程序:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

Linux 下编译 Mac 和 Windows 平台64位可执行程序:

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

python与go性能对比

为了更好的体现出来优化之后的效果,我们大概对比一下两个语言在计算密集情况下的差距。

测试:分别计算一个亿(100000000)的累加模拟大量计算。

Python代码:

import timedef run(n):sum = 0for i in range(n):sum += iprint(sum)if __name__ == '__main__':startTime = time.time()run(100000000)endTime = time.time()print("耗时:", endTime - startTime)

耗时5s左右:

Go代码:

package mainimport ("fmt""time"
)func run(n int) {sum := 0for 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版本:

>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=D:/develop/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured 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='x86_64-win32-seh-rev0, Built by MinGW-W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-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' CXXFLAGS='-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' CPPFLAGS=' -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' LDFLAGS='-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 '
Thread model: win32
gcc version 8.1.0 (x86_64-win32-seh-rev0, Built by MinGW-W64 project)

需要被编译.so文件的go代码有些要求,例如必须导入C:

package mainimport ("C" //C必须导入
)//export run
func run(n int) int{// 必须通过export 函数名格式的注释申明该函数可以被外部接口sum := 0for i := 0; i < n; i++ {sum += i}fmt.Println("我是Go代码,我跑完了,我的结果是:",sum)return sum
}func main() {//main函数中什么都不要写,和包名main要对应
}

编译为.so文件供Python调用:

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执行:

from ctypes import *
import timeif __name__ == '__main__':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开头的声明:

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 _Bool bool (1)
c_char char 单字符字节对象
c_wchar wchar_t 单字符字符串
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64 或 long long int
c_ulonglong unsigned __int64 或 unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t 或 Py_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (以 NUL 结尾) 字节串对象或 None
c_wchar_p wchar_t * (以 NUL 结尾) 字符串或 None
c_void_p void * 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的实际返回值类型即可:

from ctypes import *
import timeif __name__ == '__main__':beginTime = time.time()s = CDLL("s1.so")  # 加载s1.so文件# 根据查表,C中的long long,对应的ctypes 是 c_longlongs.run.restype = c_longlong  # 声明.so的run函数返回值类型,固定格式result = s.run(100000000)  # 调用Go生成的.so文件里面的run函数print(result)endTime = time.time()print("耗时:", endTime - beginTime)

现在结果就没有问题了。

处理返回值为字符串的情况

s2.go的代码:

package mainimport ("C" //C必须导入
)//export speak
func speak(n int) string {return "996好累呀,难得休息一天,好好休息 "
}
func main() {//main函数中什么都不要写,和包名main要对应
}

查看s2.h:

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的结构体:

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完整代码:

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__ == '__main__':beginTime = time.time()s = CDLL("s2.so")  # 加载s1.so文件s.speak.restype = GoStringspeakStr = 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代码:

package mainimport ("C" //C必须导入"strconv"
)//export speak
func speak(n int) string {s := "996好累呀,难得休息一天,好好休息 " + strconv.Itoa(n)return s
}func main() {//main函数中什么都不要写,和包名main要对应
}

重复以上步骤,运行s3.py,出现如下错误:

这是因为此时的string不是c语言的对象,而是go语言的对象,修改为如下代码即可:

s2.go代码:

package mainimport ("C" //C必须导入"strconv"
)//export speak
func speak(n int) *C.char {s := "996好累呀,难得休息一天,好好休息 " + strconv.Itoa(n)return C.CString(s)
}func main() {//main函数中什么都不要写,和包名main要对应
}

以上代码申明返回c语言的字符串类型,查看.h文件可以看到:

extern char* speak(GoInt n);

那么s3.py代码只需修改为:

from ctypes import *
import timeif __name__ == '__main__':beginTime = time.time()s = CDLL("s3.so")  # 加载s1.so文件s.speak.restype = c_char_pspeakStr = s.speak(7).decode("utf-8")print("speak:", speakStr)endTime = time.time()print("耗时:", endTime - beginTime)

顺利运行。

使用ctypes访问C代码

基本示例

实现两数求和的C代码add.c文件:

#include <stdio.h>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文件:

#For Linux or windows
gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c#For Mac
gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c

在Python代码中来调用它:

from ctypes import *adder = CDLL('adder.so')res_int = adder.add_int(4, 5)
print("Sum of 4 and 5 = " + str(res_int))add_float = adder.add_float
add_float.restype = c_float
a = c_float(5.5)
b = c_float(4.1)
print("Sum of 5.5 and 4.1 = ", str(add_float(a, b)))

输出:

Sum of 4 and 5 = 9
Sum of 5.5 and 4.1 =  9.600000381469727

ctypes接口允许我们在调用C函数时参数使用原生Python中默认的字符串型和整型,而对于其他类似布尔型和浮点型这样的类型,必须要使用正确的ctype类型才可以。如向adder.add_float()函数传参时, 要先将Python中的float类型转化为c_float类型,然后才能传送给C函数。

复杂示例

编码c代码,sample.c文件的内容为:

#include <math.h>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:

gcc -shared -o sample.so sample.c

在 sample.so 所在文件相同的目录编写python代码,sample.py文件

import ctypes_mod = ctypes.cdll.LoadLibrary('sample.so')# int gcd(int, int)
gcd = _mod.gcd
gcd.argtypes = (ctypes.c_int, ctypes.c_int)
gcd.restype = ctypes.c_int# int in_mandel(double, double, int)
in_mandel = _mod.in_mandel
in_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)
# 定义 'double *'参数的类型
class DoubleArrayType:def from_param(self, param):typename = type(param).__name__if hasattr(self, 'from_' + typename):return getattr(self, 'from_' + typename)(param)elif isinstance(param, ctypes.Array):return paramelse:raise TypeError("Can't convert %s" % typename)# Cast from array.array objectsdef from_array(self, param):if param.typecode != 'd':raise TypeError('must be an array of doubles')ptr, _ = param.buffer_info()return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))# Cast from lists/tuplesdef from_list(self, param):val = ((ctypes.c_double) * len(param))(*param)return valfrom_tuple = from_list# Cast from a numpy arraydef 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_ = [('x', ctypes.c_double),('y', ctypes.c_double)]# double distance(Point *, Point *)
distance = _mod.distance
distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))
distance.restype = ctypes.c_double

然后就可以加载并使用里面定义的C函数了,编写test.py

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]))

执行结果:

sample.gcd(35, 42): 7
sample.in_mandel(0, 0, 500): 1
sample.in_mandel(2.0, 1.0, 500): 0
sample.divide(42, 8): (5, 2)
sample.avg([1, 2, 3]): 2.0
sample.distance(p1, p2): 2.0

复杂示例解析

加载c函数库

如果C函数库被安装为一个标准库,那么可以使用 ctypes.util.find_library() 函数来查找它所在的位置:

>>> from ctypes.util import find_library
>>> find_library('m')
'libm.so.6'
>>> find_library('pthread')
'libpthread.so.0'
>>> find_library('sample')

如果是非标准库,则需要知道C函数库的位置,然后使用 ctypes.cdll.LoadLibrary() 来加载它:

_mod = ctypes.cdll.LoadLibrary(_path) #_path是C函数库的位置,全路径和相对路径都可以

指定参数和返回值的类型

函数库被加载后,需要提取特定的符号指定它们的类型。例如:

# int in_mandel(double, double, int)
in_mandel = _mod.in_mandel
in_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不能明确的对应上来,例如:

# 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 = 0
divide(10, 3, x)

这种写法违反了Python对于整数的不可更改原则,并且可能会导致整个解释器陷入一个黑洞中。

对于涉及到指针的参数,通常需要先构建一个相应的ctypes对象再作为参数传入:

x = ctypes.c_int()
divide(10, 3, x)

ctypes.c_int 实例是作为指针被传进去的,跟普通Python整数不同的是,c_int 对象是可以被修改的。

.value属性可被用来获取或更改这个值:

x.value

对于这种不像Python的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_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对象:

def from_param(self, param):typename = type(param).__name__if hasattr(self, 'from_' + typename):return getattr(self, 'from_' + typename)(param)elif isinstance(param, ctypes.Array):return paramelse:raise TypeError("Can't convert %s" % typename)

参数的类型名被提取出来并被用于分发到一个更具体的方法中去。

例如,如果参数是一个列表,那么 typename 就是 list ,然后 from_list 方法就会被调用。

def from_list(self, param):val = ((ctypes.c_double) * len(param))(*param)return val

演示通过交互式命令行将list列表转换为 ctypes 数组:

>>> import ctypes
>>> nums = [1, 2, 3]
>>> a = (ctypes.c_double * len(nums))(*nums)
>>> a
<__main__.c_double_Array_3 object at 0x10069cd40>
>>> a[0]
1.0
>>> a[1]
2.0
>>> a[2]
3.0

如果参数是一个numpy数组,那么 typename 就是 ndarray,然后 from_ndarray方法就会被调用:

def from_ndarray(self, param):return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

如果参数是一个数组对象,那么 typename 就是 array,然后 from_array方法就会被调用:

def from_array(self, param):if param.typecode != 'd':raise TypeError('must be an array of doubles')ptr, _ = param.buffer_info()return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))

对于数组对象,buffer_info()方法可以获取到数组对应的内存地址和长度,ctypes.cast()可以将内存地址转换为ctypes 指针对象:

>>> import array
>>> a = array.array('d',[1,2,3])
>>> a
array('d', [1.0, 2.0, 3.0])
>>> ptr,length = a.buffer_info()
>>> ptr
4298687200
>>> length
3
>>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
<__main__.LP_c_double object at 0x10069cd40>

通过定义 DoubleArrayType类并在 avg() 类型签名中使用它,那么这个函数就能接受多个不同的类数组输入了:

import sample
sample.avg([1,2,3])
2.0
sample.avg((1,2,3))
2.0
import array
sample.avg(array.array('d',[1,2,3]))
2.0
import numpy
sample.avg(numpy.array([1.0,2.0,3.0]))
2.0

参数包含结构体

对于结构体,只需要简单的定义一个类,包含相应的字段和类型即可:

class Point(ctypes.Structure):_fields_ = [('x', ctypes.c_double),('y', ctypes.c_double)]

类型签名绑定只需:

# double distance(Point *, Point *)
distance = _mod.distance
distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))
distance.restype = ctypes.c_double

一旦类被定义后,就可以在类型签名中或者是需要实例化结构体的代码中使用它。例如:

>>> p1 = sample.Point(1,2)
>>> p2 = sample.Point(4,5)
>>> p1.x
1.0
>>> p1.y
2.0
>>> sample.distance(p1,p2)
4.242640687119285

将函数指针转换为可调用对象

获取C函数的内存地址(经测试,在linux上支持,windows上不支持):

import ctypes
lib = ctypes.cdll.LoadLibrary(None)
# 获取C语言math库的sin()函数的地址
addr = ctypes.cast(lib.sin, ctypes.c_void_p).value
print(addr)

上述代码在linux下得到整数140266666308000,而在Windows下会报错TypeError: LoadLibrary() argument 1 must be str, not None

有了函数的内存地址,就可以将它转换成一个Python可调用对象:

# 将函数地址转换成一个Python的可调用对象,参数为函数的返回值类型和参数类型
functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
sin = functype(addr)
print(sin)

CFUNCTYPE() 的第一个参数是返回类型,接下来的参数是参数类型,生成的对象被当做普通的可通过 ctypes 访问的函数来使用。

打印:<CFunctionType object at 0x7f9261becb38>

调用测试:

>>> import math
>>> math.pi
3.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可调用对象,并执行函数:

>>> from llvm.core import Module, Function, Type, Builder
>>> mod = Module.new('example')
>>> f = Function.new(mod,Type.function(Type.double(), [Type.double(), Type.double()], False), 'foo')
>>> block = f.append_basic_block('entry')
>>> 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)
<llvm.core.Instruction object at 0x10078e990>
>>> from llvm.ee import ExecutionEngine
>>> engine = ExecutionEngine.new(mod)
>>> ptr = engine.get_pointer_to_function(f)
>>> ptr
4325863440
>>> 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

#include <stdio.h>void print_chars(char *s) {printf("%s\n",s);while (*s) {printf("%2x ", (unsigned char) *s);s++;}printf("\n");
}int main() {print_chars("Hello");
}

执行结果:

> gcc str1.c&a.exe
Hello
48 65 6c 6c 6f

编译c程序为so文件:

gcc -shared -o str1.so str1.c

用python调用:

import ctypes_mod = ctypes.cdll.LoadLibrary('str1.so')
# void print_chars(char *s)
print_chars = _mod.print_chars
print_chars.argtypes = (ctypes.c_char_p,)print_chars(b'Hello')
print_chars(b'Hello\x00World')

打印结果:

Hello
48 65 6c 6c 6f
Hello
48 65 6c 6c 6f

不能直接传入python的字符串类型,例如:print_chars('Hello World')

否则会报错:ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type

如果需要传递字符串而不是字节,可以先编码成 UTF-8 转成字节:

>>> print_chars('Hello World'.encode('utf-8'))
Hello World
48 65 6c 6c 6f 20 57 6f 72 6c 64

python调用go或c语言相关推荐

  1. python调用golang 数组_Go 语言数组复制

    Go 语言数组复制 Golang编程语言中的数组与其他编程语言非常相似.在程序中,有时我们需要存储一组相同类型的数据,例如学生评分列表.这种类型的集合使用数组存储在程序中.数组是固定长度的序列,用于将 ...

  2. Python语言学习:利用python语言实现调用内部命令(python调用Shell脚本)—命令提示符cmd的几种方法

    Python语言学习:利用python语言实现调用内部命令(python调用Shell脚本)-命令提示符cmd的几种方法 目录 利用python语言实现调用内部命令-命令提示符cmd的几种方法 T1. ...

  3. python调用rust_转 从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例

    导读:Python 被很多互联网系统广泛使用,但在另外一方面,它也存在一些性能问题,不过 Sentry 工程师分享的在关键模块上用另外一门语言 Rust 来代替 Python 的情况还是比较罕见,也在 ...

  4. python调用C语言函数(方法)的几种方法

    1. 使用ctypes 可能是Python调用C方法中最简单的一种 2. 使用SWIG 是Python中调用C代码的另一种方法.在这个方法中,开发人员必须编写一个额外的接口文件来作为SWIG的入口. ...

  5. Python调用C语言

    Python中的ctypes模块可能是Python调用C方法中最简单的一种.ctypes模块提供了和C语言兼容的数据类型和函数来加载dll文件,因此在调用时不需对源文件做任何的修改.也正是如此奠定了这 ...

  6. go加载python_python培训 | python调用go语言来提速

    在写一些对性能要求十分严格的功能的时候,python往往力不从心.毕竟是一个解释性的语言,没有办法和变异性的语言去比较速度和内存占用率.但是python的本身的特定就使得我们可以将耗时的操作移交给编译 ...

  7. 如何将c语言程序封装供python调用_C++调用python

    C++调用python 在C/C++中嵌入Python,可以使用Python提供的强大功能,通过嵌入Python可以替代动态链接库形式的接口,这样可以方便地根据需要修改脚本代码,而不用重新编译链接二进 ...

  8. python调用c语言函数_从Python调用C函数

    python调用c语言函数 We can call a C function from Python program using the ctypes module. 我们可以使用ctypes模块从P ...

  9. python调用R语言

    R语言有很强大的统计绘图函数,python的开发能力强,数据处理速度比R快.在平时使用python处理数据,使用R统计分析.于是想着怎么使用python来调用R,毕竟python还不能取代R,它们是相 ...

最新文章

  1. 深入理解JVM虚拟机(十):Java内存模型与多线程
  2. Oracle-UNDO表空间解读
  3. unity 中让Text的文字动态刷新形式
  4. STM32下载库资料
  5. Codeforces Round #401 (Div. 1) C(set+树状数组)
  6. 将某个字段改为不重复(unique)
  7. 吴恩达DeepLearningCourse2-改善深层神经网络:超参数调试、正则化以及优化
  8. js变量和java变量相等,js中变量和jsp中java代码中变量互相访问解决方案
  9. JAVA中使用bos做视频上传_JAVA语言之搭建物流BOS项目骨架
  10. 前端学习(2815):小程序学习之开发者工具介绍
  11. 安装串口设备驱动时遇到 Windows 无法验证此设备所需的驱动程序的数字签名。最近的硬件或软件更改安装的文件可能未正确签名或已损坏,或者可能是来自未知来源的恶意软件. 问题该如何处理?...
  12. Redmi 10X Pro 5G曝光:四种配色、两种存储版本可选
  13. 表格如何调出好看的样式?
  14. 基于微信小程序的考勤打卡系统
  15. 树莓派支持的最小linux,世界上最小的电脑:树莓派
  16. 8052单片机英文缩写参考全称
  17. QQ心跳包格式分析 监听局域网QQ号代码
  18. 什么是锁?有几种锁?怎么用锁?
  19. Unity生成随机数
  20. 研发二部二组-问题点归纳

热门文章

  1. FFmpeg —— 屏幕录像和录音并推流(命令行的方式)
  2. es6中Generator函数的理解
  3. 浏览器禁止video视频另存下载
  4. 一个有用的产品路线图应该是什么样的?
  5. 2022年材料员-岗位技能(材料员)考试题库及模拟考试
  6. 计算机桌面移至其它盘,Windows如何迁移桌面至其它盘
  7. 2021-10-22 中值定理的应用
  8. 解决七牛云链接失效以及批量下载图片并迁移阿里云 OSS
  9. vscode配置护眼色
  10. 使用 MyBatis-Plus 分页查询