用Golang為Python編寫模塊


Go里面需要顯示的引入C模塊, 讓編譯器支持生成動態鏈接庫, 並且在代碼中可以使用C語言的數據類型,這個至關重要. Calling Go code from Python code 摘取一個最簡單例子

//libadd.go
package main

import "C"

//export add
func add(left, right int) int {
    return left + right
}

func main() {
}
go build -buildmode=c-shared -o libadd.so libadd.go
from ctypes import cdll
lib = cdll.LoadLibrary('./libadd.so')
print("Loaded go generated SO library")
result = lib.add(2, 3)
print(result)

The cgo export command is documented in go doc cgo, section "C references to Go". Essentially, write //export FUNCNAME before the function definition

有這么一段話, 需要顯式注釋//export add 把 add函數公開給C調用

本以為很簡單的就能用, 興致滿滿地把例子改一下, 改為簡單的處理字符串的時候, 卻發現跑不起來了.

//libadd.go
package main

import "C"

//export add
func add(left, right string) string {
    return left + right
}

func main() {
}
from ctypes import CDLL
lib = CDLL('./libadd.so')
print("Loaded go generated SO library")
result = lib.add("Hello", "World")
print(result)
  • 這時候運行是出錯的

    再次翻看資料發現這么一句話:

The python code is really short and this is only passing an integer back and forth (more complex string and struct cases are much more challenging).

這說明處理字符串的時候並不是簡單改成string類型就可以.這時候翻開了BUILDING PYTHON MODULES WITH GO 1.5 , 這時能找到的最全面的資料, 可惜里面的過程都過於復雜, 整個思路是用Go去寫C code, 類似寫解釋器一樣, 去抽象出PyObject然后按照API標准來注冊、處理、返回.我僅是希望以動態鏈接庫 的方式來能調用就可以了.

我開始思考, 為何例子中使用int類型就可以, 我改成一個簡單的接收string 返回string 卻一直失敗. py是利用ctypes來跟so模塊進行交互, 這里存在一個代碼的翻譯過程 Py -> C -> Go, 我能想到的對於字符串數據類型的處理不一樣原因引起(后面事實證明了我的猜想).那么思考一下, Py中的字符串傳遞到Go里面去使用什么類型來接收呢? 翻閱了大量資料, 所有答案在Python Doc 官網關於ctypes模塊中有能找到.我們來看一下這圖:


001.png


這里可以很清楚的看到Python3 ctypes中字符串 bytesstring 是對應的兩種指針類型.同時提供了argtypesrestype 來顯式轉換動態鏈接庫中函數的參數和返回類型.(參考StackOverFlow)

這時候按照思考的流程來修改代碼

//libadd.go
package main

import "C"

//export add
func add(left, right *C.char) *C.char {
      // bytes對應ctypes的c_char_p類型,翻譯成C類型就是 char *指針
      merge := C.GoString(left) + C.GoString(right)
    return C.CString(merge)
}

func main() {}

重新編譯

go build -buildmode=c-shared -o libadd.so libadd.go

Python中引用

import ctypes
add = ctypes.CDLL('./libadd.so').add
# 顯式聲明參數和返回的期望類型
add.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
add.restype = ctypes.c_char_p
left = b"Hello"
right = b"World"
print(add(left, right))

正確輸出結果:

b"HelloWorld"

就這樣, 一個基本的模塊就完成, 只要關注傳入參數和返回結果的數據類型處理, 我只需要豐富函數的處理邏輯,Go模塊中函數內部實現對於Python是透明,只要參數正確即可.其中關於 cgo更多的信息, 大家可以自行查閱Golang.org

總結

  1. Python與Go之間的參數傳遞, 處理非INT型時需要都轉為對應的C類型
  2. ctypes需要顯式地聲明DLL函數的參數和返回期望的數據類型
  3. 注意在Python3中字符串bytes和string的區別
  4. Go模塊需要//export 聲明外部可調用
  5. Go處理C的類型是需要顯式轉換


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM