go 中的 cgo 模块可以让 go 无缝调用 c 或者 c++ 的代码,而 python 本身就是个 c 库,自然也可以由 cgo 直接调用,前提是指定正确的编译条件,如 Python.h 头文件(),以及要链接的库文件。本文以 Ubuntu 18.04 作为开发和运行平台进行演示。
其实在使用 cgo 之前,笔者也考虑过使用 grpc 的方式。比如可以将需要调用的 python 代码包装成一个 grpc server 端,然后再使用 go 编写对应的 clIEnt 端,这样考虑的前提是,go 调用 python 代码本来就是解一时之困,而且引入语言互 *** 作后,对于项目维护和开发成本控制都有不小的影响,如果直接使用 grpc 生成编程语言无感知的协议文件,将来无论是重构或使用其他语言替换 python 代码,都是更加方便,也是更加解耦的。所以 grpc 也是一种比较好的选择。至于通信延迟,老实说既然已经设计语言互 *** 作,本机中不到毫秒级的损失其实也是可以接受的。
接下来进入正题。
1. 针对 python 版本安装 python-dev
sudo apt install python3.6-dev
系统未默认安装 python3.x 的开发环境,所以假如要通过 cgo 调用 python,需要安装对应版本的开发包。
2. 指定对应的cgo CFLAGS 和 LDFLAGS 选项
对于未由 c 包装的 python 代码,python-dev 包中内置了 python-config 工具用于查看编译选项。
python3.6-config --cflagspython3.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 代码的配置为
//#cgo CFLAGS : -I./ -I/usr/include/python3.6m//#cgo LDFLAGS: -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
type PyObject struct { ptr *C.PyObject}func togo(obj *C.PyObject) *PyObject { if obj == nil { return nil } return &PyObject{ptr: obj}}func topy(self *PyObject) *C.PyObject { if self == nil { return nil } return self.ptr}
3.1 python 环境的启动与终结
func Initialize() error { if C.Py_IsInitialized() == 0 { C.Py_Initialize() } if C.Py_IsInitialized() == 0 { return fmt.Errorf("python: Could not initialize the python interpreter") } if C.PyEval_ThreadsInitialized() == 0 { C.PyEval_InitThreads() } if C.PyEval_ThreadsInitialized() == 0 { return fmt.Errorf("python: Could not initialize the GIL") } return nil}func Finalize() error { C.Py_Finalize() return nil}
3.2 包路径与模块导入
func InsertExtraPackageModule(dir string) *PyObject { sysModule := importModule("sys") path := sysModule.GetAttrString("path") cstr := C.CString(dir) defer C.free(unsafe.Pointer(cstr)) C.PyList_Insert(topy(path),C.Py_ssize_t(0),topy(togo(C.PyBytes_FromString(cstr)))) return importModule(dir)}func importModule(name string) *PyObject { c_name := C.CString(name) defer C.free(unsafe.Pointer(c_name)) return togo(C.Pyimport_importModule(c_name))}func (self *PyObject) GetAttrString(attr_name string) *PyObject { c_attr_name := C.CString(attr_name) defer C.free(unsafe.Pointer(c_attr_name)) return togo(C.PyObject_GetAttrString(self.ptr,c_attr_name))}
3.3 数据类型转换
func PyStringFromGoString(v string) *PyObject { cstr := C.CString(v) defer C.free(unsafe.Pointer(cstr)) return togo(C.PyBytes_FromString(cstr))}func PyStringAsGoString(self *PyObject) string { c_str := C.PyBytes_Asstring(self.ptr) return C.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 的文档资料其实并不多,所以希望本文能给大家带来一些帮助。
总结以上是内存溢出为你收集整理的Golang 调用 Python 代码全部内容,希望文章能够帮你解决Golang 调用 Python 代码所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)