golang調用python實戰

簡介

go-python

Python提供了豐富的C-API。而C和Go又可以通過cgo無縫集成。所以,直接通過Golang調用libpython,就可以實現Go調Python的功能了。但是過程比較複雜,而go-python提供了針對CPython-2的C-API提供了native-binding能力,方便實現了Go到Python的調用。

但是目前,go-python只支持python2.7。

pkg-config

go-python使用pkg-config來獲取python的頭文件及庫信息。

一般來說,如果庫的頭文件不在/usr/include目錄中,那麼在編譯的時候需要用-I參數指定其路徑。由於同一個庫在不同系統上可能位於不同的目錄下,用戶安裝庫的時候也可以將庫安裝在不同的目錄下,所以即使使用同一個庫,由於庫的路徑的不同,造成了用-I參數指定的頭文件的路徑和在連接時使用-L參數指定lib庫的路徑都可能不同,其結果就是造成了編譯命令界面的不統一。pkg-config就是用來解決編譯連接界面不統一問題的一個工具。

pkg-config的基本思想是通過庫提供的一個.pc文件獲得庫的各種必要信息的,包括版本信息、編譯和連接需要的參數等。這樣,不管庫文件安裝在哪,通過庫對應的.pc文件就可以準確定位,可以使用相同的編譯和連接命令,使得編譯和連接界面統一。

環境配置(MAC爲例)

1、本地找到python-2.7.pc文件。如果沒有則創建一個。特別注意,prefix要指定爲python2.7的library路徑。

# See: man pkg-config
prefix=/System/Library/Frameworks/Python.framework/Versions/2.7
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: Python
Description: Python library
Requires:
Version: 2.7
Libs.private: -ldl  -framework CoreFoundation
Libs: -L${libdir} -lpython2.7
Cflags: -I${includedir}/python2.7

2、將python-2.7.pc路徑添加到環境變量$PKG_CONFIG_PATH中

3、驗證結果:pkg-config --cflags -- python-2.7
-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7

4、執行make VERBOSE=1,第一次執行可能會提示go-python命令找不到。將gopath的bin路徑添加到環境變量$PATH即可。如下圖ut通過,說明配置成功了。

image

5、單獨執行test。需要添加下PYTHONPATH環境變量,否則將導入不了python包。

export PYTHONPATH=.:$PYTHONPATH

cd go-python/tests/kw-args
go run main.go
importing kwargs...
args=() kwds={}
args=() kwds={'a': 3}

樣例

使用python的cPickle模塊,通過 dumps將python對象序列化保存到一個字符串變量中,通過loads從字符串變量中載入python對象。

package main

import (
    "fmt"
    "github.com/sbinet/go-python"
)

// 初始化go-python
func init() {
    err := python.Initialize()
    if err != nil {
        panic(err.Error())
    }
}

func main() {
    gostr := "foo"  //定義goloang字符串
    pystr := python.PyString_FromString(gostr)  //將golang字符串專程python字符串
    str := python.PyString_AsString(pystr)     //將python字符串,再轉爲golang字符串。
    fmt.Println("hello [", str, "]")

    pickle := python.PyImport_ImportModule("cPickle")  //導入cPickle模塊
    if pickle == nil {
        panic("could not import 'cPickle'")
    }

    dumps := pickle.GetAttrString("dumps")   //獲取dumps函數
    if dumps == nil {
        panic("could not retrieve 'cPickle.dumps'")
    }
  defer dumps.DecRef()   //減少引用計數,釋放資源。
    
    out := dumps.CallFunctionObjArgs("O", pystr)  //針對python字符串進行dumps操作。
    if out == nil {
        panic("could not dump pystr")
    }
  defer out.DecRef()    
 
    fmt.Printf("cPickle.dumps(%s) = %q\n", gostr,
        python.PyString_AsString(out),
    )
    
    loads := pickle.GetAttrString("loads")  //獲取loads函數
    if loads == nil {
        panic("could not retrieve 'cPickle.loads'")
    }
  defer loads.DecRef()
    out2 := loads.CallFunctionObjArgs("O", out)  //將dumps結果重新loads
    if out2 == nil {
        panic("could not load back out")
    }
  defer out2.DecRef()

    fmt.Printf("cPickle.loads(%q) = %q\n",
        python.PyString_AsString(out),
        python.PyString_AsString(out2),
    )
}

機制簡介

整個go-python的核心在於處理PyObject跟golang類型的關係。在Python內部,PyObject結構體用來保存全部對象共同的數據成員,以及實現GC機制所須要的一些輔助字段等,所以說PyObjectPython對象機制的基礎。

sequence.go: 處理了PyObject跟golang內置類型的轉換。典型的例如:PyString_FromString是將golang string轉換爲python string,即PyObject;PyString_AsString是將PyObject轉換爲golang string。
object.go:關於PyObject的一些核心操作。例如:獲取函數對象GetAttr,及響應函數對象的調用CallFunctionObjArgs等。

其本質是對python C擴展的封裝。如下所示:

func (self *PyObject) GetAttr(attr_name *PyObject) *PyObject {
    return togo(C.PyObject_GetAttr(self.ptr, attr_name.ptr))
}

注意事項:

所有的PyObject對象使用結束,需要主動調用DecRef,通過減少引用計數的方式釋放對象,否則會產生內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章