Golang如何调用Python代码详解
前言
Python是时髦的机器学习御用开发语言,Golang是大红大紫的新时代后端开发语言。Python很适合让搞算法的写写模型,而Golang很适合提供API服务,两位同志都红的发紫,这里就介绍一下正确搅基的办法。
go中的cgo模块可以让go无缝调用c或者c++的代码,而python本身就是个c库,自然也可以由cgo直接调用,前提是指定正确的编译条件,如Python.h头文件(),以及要链接的库文件。本文以Ubuntu18.04作为开发和运行平台进行演示。
其实在使用cgo之前,笔者也考虑过使用grpc的方式。比如可以将需要调用的python代码包装成一个grpcserver端,然后再使用go编写对应的client端,这样考虑的前提是,go调用python代码本来就是解一时之困,而且引入语言互操作后,对于项目维护和开发成本控制都有不小的影响,如果直接使用grpc生成编程语言无感知的协议文件,将来无论是重构或使用其他语言替换python代码,都是更加方便,也是更加解耦的。所以grpc也是一种比较好的选择。至于通信延迟,老实说既然已经设计语言互操作,本机中不到毫秒级的损失其实也是可以接受的。
接下来进入正题。
Golang调用Python代码
1.针对python版本安装python-dev
sudoaptinstallpython3.6-dev
系统未默认安装python3.x的开发环境,所以假如要通过cgo调用python,需要安装对应版本的开发包。
2.指定对应的cgoCFLAGS和LDFLAGS选项
对于未由c包装的python代码,python-dev包中内置了python-config工具用于查看编译选项。
python3.6-config--cflags python3.6-config--ldflags
以下是对应的输出
-I/usr/include/python3.6m-I/usr/include/python3.6m -Wno-unused-result-Wsign-compare-g-fdebug-prefix-map=/build/python3.6-MtRqCA/python3.6-3.6.6=.-specs=/usr/share/dpkg/no-pie-compile.specs-fstack-protector-Wformat-Werror=format-security -DNDEBUG-g-fwrapv-O3-Wall
-L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu-L/usr/lib-lpython3.6m-lpthread-ldl -lutil-lm -xlinker-export-dynamic-Wl,-O1-Wl,-Bsymbolic-functions
低版本的python也可以在安装开发包后,使用对应的python-config命令打印依赖配置。由于cgo默认使用的编译器不是gcc,所以输出中的部分选项并不受支持,所以最后cgo代码的配置为
//#cgoCFLAGS:-I./-I/usr/include/python3.6m
//#cgoLDFLAGS:-L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu-L/usr/lib-lpython3.6m-lpthread-ldl -lutil-lm
//#include"Python.h"
import"C"
3.部分示例代码
3.0映射PyObject
typePyObjectstruct{ ptr*C.PyObject } functogo(obj*C.PyObject)*PyObject{ ifobj==nil{ returnnil } return&PyObject{ptr:obj} } functopy(self*PyObject)*C.PyObject{ ifself==nil{ returnnil } returnself.ptr }
3.1python环境的启动与终结
funcInitialize()error{ ifC.Py_IsInitialized()==0{ C.Py_Initialize() } ifC.Py_IsInitialized()==0{ returnfmt.Errorf("python:couldnotinitializethepythoninterpreter") } ifC.PyEval_ThreadsInitialized()==0{ C.PyEval_InitThreads() } ifC.PyEval_ThreadsInitialized()==0{ returnfmt.Errorf("python:couldnotinitializetheGIL") } returnnil } funcFinalize()error{ C.Py_Finalize() returnnil }
3.2包路径与模块导入
funcInsertExtraPackageModule(dirstring)*PyObject{ sysModule:=ImportModule("sys") path:=sysModule.GetAttrString("path") cstr:=C.CString(dir) deferC.free(unsafe.Pointer(cstr)) C.PyList_Insert(topy(path),C.Py_ssize_t(0),topy(togo(C.PyBytes_FromString(cstr)))) returnImportModule(dir) } funcImportModule(namestring)*PyObject{ c_name:=C.CString(name) deferC.free(unsafe.Pointer(c_name)) returntogo(C.PyImport_ImportModule(c_name)) } func(self*PyObject)GetAttrString(attr_namestring)*PyObject{ c_attr_name:=C.CString(attr_name) deferC.free(unsafe.Pointer(c_attr_name)) returntogo(C.PyObject_GetAttrString(self.ptr,c_attr_name)) }
3.3数据类型转换
funcPyStringFromGoString(vstring)*PyObject{ cstr:=C.CString(v) deferC.free(unsafe.Pointer(cstr)) returntogo(C.PyBytes_FromString(cstr)) } funcPyStringAsGoString(self*PyObject)string{ c_str:=C.PyBytes_AsString(self.ptr) returnC.GoString(c_str) } ...
可以看到形似C.Py*的方法都是由cgo模块编译调用的,这些方法也是python暴露的C-API,而这里的示例就到此为止,其他诸如调用python模块方法的功能文档里也描述得十分详细,尽管实施起来仍然有些麻烦。
但是请注意C-API的2.x与3.x版本仍有不同,比如2.x版本中的字符串操作类型PyString_*在3.x中便被重命名为PyBytes_*。
关注过go与python互操作功能的同学应该注意到上述的示例代码部分来自go-python这个开源项目,有兴趣的同学也可以关注一下。这个项目基于python2.7,其中暴露的api诸如字符串转换也是基于python2.x版本,所以针对于更流行的python3.x项目,大家就需要自己按照上文方法做一些修改了。
实际工作中,语言的互操作场景确实很让人感觉头疼,而cgo的文档资料其实并不多,所以希望本文能给大家带来一些帮助。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。