go-python3使用指南与踩坑记录
简介
在开发工作中有时候会有需要和其他语言进行交互的需求,笔者前段时间就接到了需要在go中调用python3的需求,这种需求笔者想到了两种解法,一是直接在代码中调用python3,二是使用shell命令执行python3脚本。在本文中主要介绍了在go中使用go-python3这个库调用python3的一些实践历程与踩坑经历。关于使用脚本在另一篇文章:https://blog.csdn.net/LuciferMS/article/details/121888491
环境搭建
github上有现成的go调用python3开源框架,https://github.com/DataDog/go-python3,值得注意的是目前这个库只支持python3.7,笔者开始的时候电脑上装的是python3.8,所以在这个上面花费了好多时间,不过这也给了我一个教训,就是使用一个开源库的时候一定要好好看他的readme.md,留意一下有什么值得注意的地方。
go get github.com/DataDog/go-python3brew install pkg_config
程序demo
笔者的程序目录结构是这样的:
下面我们来看这个库的具体使用demo:
1.go代码 test_python3.go
package main
import (
"fmt"
"github.com/DataDog/go-python3"
"log"
"os"
)
func ImportModule(dir, name string) *python3.PyObject {
sysModule := python3.PyImport_ImportModule("sys")
path := sysModule.GetAttrString("path")
pathStr, _ := pythonRepr(path)
log.Println("before add path is " + pathStr)
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString(""))
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString(dir))
pathStr, _ = pythonRepr(path)
log.Println("after add path is " + pathStr)
return python3.PyImport_ImportModule(name)
}
func main(){
python3.Py_Initialize()
if !python3.Py_IsInitialized() {
fmt.Println("Error initializing the python interpreter")
os.Exit(1)
}
path, _ := os.Getwd()
helloPy := ImportModule(path + "/py3", "test_python3")
if helloPy == nil {
log.Fatalf("helloPy is nil")
return
}
helloFunc := helloPy.GetAttrString("test_print_name")
if helloFunc == nil {
log.Fatalf("helloFunc is nil")
}
var args = python3.PyTuple_New(1)
python3.PyTuple_SetItem(args, 0, python3.PyUnicode_FromString("python3"))
helloPy3Str := helloFunc.Call(args, python3.Py_None)
if helloPy3Str == nil {
log.Fatalf("helloPy3Str is nil")
}
funcResultStr, _ := pythonRepr(helloPy3Str)
log.Println("func result: " + funcResultStr)
}
func pythonRepr(o *python3.PyObject) (string, error) {
if o == nil {
return "", fmt.Errorf("object is nil")
}
s := o.Repr()
if s == nil {
python3.PyErr_Clear()
return "", fmt.Errorf("failed to call Repr object method")
}
defer s.DecRef()
return python3.PyUnicode_AsUTF8(s), nil
}
2.python代码test_python3.py
import os
from test_import_py import f
from test_import_py import x
# import cython
def test_print_name(name):
print(os.getcwd())
y = f(1.1)
print(x)
return y
3.python&cython代码
import cython
x = 3
@cython.cfunc
@cython.exceptval(-2, check=True)
def f(x: cython.double) -> cython.double:
return x ** 2 - x
程序运行结果
程序测试点
- 如何引入执行一个python方法?
- 在python代码中引入别的模块会不会有问题
- 在python程序中写Cython会不会有问题
结果解析
python3.Py_Initialize()python3.Py_IsInitialized()print("hello")demo = 1
并发调用方法的坑
test_concurrency.go
package main
import (
"fmt"
"github.com/DataDog/go-python3"
"log"
"os"
)
func main() {
python3.Py_Initialize()
if !python3.Py_IsInitialized() {
fmt.Println("Error initializing the python interpreter")
os.Exit(1)
}
sysModule := python3.PyImport_ImportModule("sys")
path := sysModule.GetAttrString("path")
//pathStr, _ := pythonRepr2(path)
//log.Println("before add path is " + pathStr)
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString(""))
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString("./py3"))
concurrencyFile := python3.PyImport_ImportModule("test_concurrency")
if concurrencyFile == nil {
log.Fatalf("concurrency is nil")
return
}
testFunc := concurrencyFile.GetAttrString("test_func")
if testFunc == nil {
log.Fatalf("testFunc is nil")
return
}
go func() {
testFunc.Call(python3.Py_None, python3.Py_None)
for {
}
}()
//time.Sleep(10 * time.Second)
go func() {
testFunc.Call(python3.Py_None, python3.Py_None)
for {
}
}()
select {
}
}
test_concurrency.py
import time
def test_func():
print("hello")
time.sleep(10)
package main
import (
"fmt"
"github.com/DataDog/go-python3"
"log"
"os"
)
func main() {
python3.Py_Initialize()
if !python3.Py_IsInitialized() {
fmt.Println("Error initializing the python interpreter")
os.Exit(1)
}
sysModule := python3.PyImport_ImportModule("sys")
path := sysModule.GetAttrString("path")
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString(""))
python3.PyList_Insert(path, 0, python3.PyUnicode_FromString("./py3"))
concurrencyFile := python3.PyImport_ImportModule("test_concurrency")
if concurrencyFile == nil {
log.Fatalf("concurrency is nil")
return
}
testFunc := concurrencyFile.GetAttrString("test_func")
if testFunc == nil {
log.Fatalf("testFunc is nil")
return
}
go func() {
ensure := python3.PyGILState_Ensure() //获取python GIL(全局解释器)状态 0:释放 1:占用
fmt.Println(ensure)
testFunc.Call(python3.Py_None, python3.Py_None)
for {
}
}()
go func() {
for {
if python3.PyGILState_Ensure() == python3.PyGILState(0) {
testFunc.Call(python3.Py_None, python3.Py_None)
break
} else {
}
}
for {
}
}()
select {
}
}
上面代码的改动主要是添加了对python GIL状态的判断,python由于又全局解释器的存在,所以不能并行执行多线程的代码,这个并发的问题应该也是由于GIL的存在才会导致的。笔者翻阅了Cython的API文档,找到了这个方法可以获取GIL的状态,这个问题才得以解决,但是感觉这也不是最优的方案,因为这样代码还是串行执行的,后面再探究一下有没有优化的空间吧。
总结
其实现在回头看来,这段探索历程还是很有意思的,在工作中能有这样的体验当然也是相当棒的。
话说回来,笔者也不是很懂python,所以一些东西都是摸着石头过河,很多结论都带有一丝猜测在里面,有不对支持还请读者多多提点。
参考
- python GIL: https://www.cnblogs.com/traditional/p/13289905.html
- Cython的Api文档: https://docs.python.org/zh-cn/3/c-api/index.html