Go-Python
环境配置
Python :确保Python正确安装,所谓正确安装,就是在系统中能找到libpython.so(dylib),找到Python.h。一般linux直接安装python-devel(假如要通过cgo 调用python,需要安装对应版本的开发包。),mac直接用homebrew安装就可以。
Ubuntu安装命令:
1
|
sudo apt-get install python2.7-dev
|
Centos安装命令:
1
|
sudo yum install python-devel
|
Golang:Golang不需要什么特殊的处理,能找到go即可。
库的使用
Golang是静态语言,性能很好,当它不那么灵活,不好在运行时动态运行代码。Python是动态语言,非常灵活,但是性能很差。古人云:“鱼和熊掌不能兼得”。但是如今有了Go-Python,鱼和熊掌也可以兼得。
首先安装go-python
1
|
go get github.com/sbinet/go-python
|
我们使用下面的代码启动Python命令行解释器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// file test.go
package main
import (
"github.com/sbinet/go-python"
"os"
)
func init() {
err := python.Initialize()
if err != nil {
panic(err.Error())
}
}
func main() {
rc := python.Py_Main(os.Args)
os.Exit(rc)
}
|
代码很简单,先初始化,然后将命令行参数传递进Py_Main函数就可以进入Python命令行解释器,就像直接敲python命令一样

如果我们执行 go run main.go --version
就可以查看Python版本信息

接下来我们使用golang打印一下Python环境的sys.path变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package main
import (
"fmt"
"github.com/sbinet/go-python"
)
func init() {
err := python.Initialize()
if err != nil {
panic(err.Error())
}
}
func main() {
m := python.PyImport_ImportModule("sys")
if m == nil {
fmt.Println("import error")
return
}
path := m.GetAttrString("path")
if path == nil {
fmt.Println("get path error")
return
}
size := python.PyList_GET_SIZE(path)
for i := 0; i < size; i++ {
item := python.PyList_GET_ITEM(path, i)
s := python.PyString_AsString(item)
fmt.Println(s)
}
}
|
使用GetAttrString可以根据属性名获取对象的属性,相当于python中的.操作。
调用Python函数可以采用Object.Call方法,,列表参数使用Tuple来构建。
返回值用PyString_AS_STRING从Python字符串转换为C或Go的字符串。
首先调用PyImport_ImportModule导入sys包,然后取出path对象,再获取path的长度,使用循环挨个取出列表中的字符串,打印出来

我们看到默认sys.path里面没有包含当前目录,这意味着不能直接在当前目录导入模块。
接下来我们在sys.path里面加入当前目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package main
import (
"fmt"
"github.com/sbinet/go-python"
)
func init() {
err := python.Initialize()
if err != nil {
panic(err.Error())
}
}
func main() {
m := python.PyImport_ImportModule("sys")
if m == nil {
fmt.Println("import error")
return
}
path := m.GetAttrString("path")
if path == nil {
fmt.Println("get path error")
return
}
//加入当前目录,空串表示当前目录
currentDir := python.PyString_FromString("")
python.PyList_Insert(path, 0, currentDir)
size := python.PyList_GET_SIZE(path)
for i := 0; i < size; i++ {
item := python.PyList_GET_ITEM(path, i)
s := python.PyString_AsString(item)
fmt.Println(s)
}
}
|
我们在sys.path列表的头部插入了空串,表示将当前目录加入sys.path,于是当前目录成为优先查找路径。
有了上面的代码,我们可以试一试调用自定义python模块了,先写一个斐波那契级数
1
2
3
4
5
|
# fib.py
def fib(n):
if n <= 2:
return 1
return fib(n-1) + fib(n-2)
|
这是一个递归版本的实现,n的大小不能超过最大栈深,好,下面使用go调用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
package main
import (
"fmt"
"github.com/sbinet/go-python"
)
func init() {
err := python.Initialize()
if err != nil {
panic(err.Error())
}
}
func main() {
m := python.PyImport_ImportModule("sys")
if m == nil {
fmt.Println("import error")
return
}
path := m.GetAttrString("path")
if path == nil {
fmt.Println("get path error")
return
}
//加入当前目录,空串表示当前目录
currentDir := python.PyString_FromString("")
python.PyList_Insert(path, 0, currentDir)
m = python.PyImport_ImportModule("fib")
if m == nil {
fmt.Println("import error")
return
}
fib := m.GetAttrString("fib")
if fib == nil {
fmt.Println("get fib error")
return
}
out := fib.CallFunction(python.PyInt_FromLong(10))
if out == nil {
fmt.Println("call fib error")
return
}
fmt.Printf("fib(%d)=%d\n", 10, python.PyInt_AsLong(out))
}
|
因为当前目录已经插入sys.path,我们可以直接使用PyImport_ImportModule导入fib模块,然后获取fib函数对象,注意函数也是一个PyObject对象。将整数10传递进fib函数,得到结果打印出来。

接下来我们尝试在自定义模块里使用requests访问一下百度首页,如果能使用第三方Python模块,那么go-python也就比较Ok了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# reqbaidu.py
import requests
def touch_baidu():
res = requests.get("http://www.baidu.com")
return res
// test.go
package main
import (
"fmt"
"github.com/sbinet/go-python"
)
func init() {
err := python.Initialize()
if err != nil {
panic(err.Error())
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
func main() {
m := python.PyImport_ImportModule("sys")
if m == nil {
fmt.Println("import error")
return
}
path := m.GetAttrString("path")
if path == nil {
fmt.Println("get path error")
return
}
//加入当前目录,空串表示当前目录
currentDir := python.PyString_FromString("")
python.PyList_Insert(path, 0, currentDir)
m = python.PyImport_ImportModule("reqbaidu")
if m == nil {
fmt.Println("import error")
return
}
touchBaidu := m.GetAttrString("touch_baidu")
if touchBaidu == nil {
fmt.Println("get touch_baidu error")
return
}
res := touchBaidu.CallFunction()
if res == nil {
fmt.Println("call touch_baidu error")
return
}
statusCode := res.GetAttrString("status_code")
content := res.GetAttrString("content")
fmt.Println(python.PyInt_AS_LONG(statusCode))
fmt.Println(python.PyString_AS_STRING(content))
}
|
touchBaidu返回的是一个requests.Response对象,该对象里的属性status_code表示返回状态码,content属性表示返回内容。

使用场景
- 你需要一个功能,没有开源的go实现,但是python有,并且性能不是很重要
- 你需要一个脚本语言嵌入到go中,让go代码获得动态能力
- 你想使用Cython干一些hack的事但是又不想撸C语言
缺陷
- python虚拟机是全局的,意味着线程不安全,在必要的地方要使用GIL保护
- python会拖慢golang的性能,在性能重要的场合谨慎使用
- python虚拟机会额外消耗不少内存
基本原理
参考:
https://zhuanlan.zhihu.com/p/33445304