如何利用Golang为Python编写模块

前言

​ 由于公司的Python项目中有关于支付签名与验签的模块,是自定的一些内部逻辑,基于安全性考虑, 希望改用C/C++或者Go 来重构该部分模块,做到加解签过程透明,上层代码只需要关心结果. 由于最近开始了Golang的学习,就尝试完成这部分工作,整个过程都是边踩坑边完成,下面以样例代码来分享一下整个过程的思路.

记录

​ 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的类型是需要显式转换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,923评论 18 139
  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,628评论 1 118
  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,768评论 0 5
  • 前言 Python的创始人为Guido van Rossum。1989年圣诞节期间,在阿姆斯特丹,Guido为了打...
    依依玖玥阅读 3,602评论 6 37
  • 两本不错的书: 《Python参考手册》:对Python各个标准模块,特性介绍的比较详细。 《Python核心编程...
    静熙老师哈哈哈阅读 3,386评论 0 80