Go语言和 Python 语言作为当下最红的语言,使用 Go 语言来为Python语言编写模块是很多用户梦寐以求的方式。这里我们将演示如何通过Go语言来为Python脚本语言编写扩展模块。
Python的本地模块采用 C语言 接口规范,因此Go语言编写的函数也需要以C语言接口规范导出。Go语言的CGO特性很早就实现了将Go语言编写为C语言的静态库或动态库。关于CGO的使用细节请参考作者编写的《Go语言高级编程》的第二章CGO编程部分。
## 基于ctypes
Python内置了非常丰富的模块,其中ctypes支持直接从C动态库调用函数。为了演示如何基于ctypes技术来扩展模块,我们需要先用Go语言创建一个C动态库。
我们使用的是之前出现过的例子:
```go
// main.go
package main
import "C"
import "fmt"
func main() {}
//export SayHello
func SayHello(name *C.char) {
fmt.Printf( "hello %s! \n " , C.GoString(name))
}
```
其中只导出了一个SayHello函数,用于打印字符串。通过以下命令基于上述Go代码创建say-hello.so动态库:
```
go build -buildmode=c-shared -o say-hello.so main.go
```
现在我们就可以通过ctypes模块调用say-hello.so动态库中的SayHello函数了:
```python
// hello.py
import ctypes
libso = ctypes.CDLL( "./say-hello.so" )
SayHello = libso.SayHello
SayHello.argtypes = [ctypes.c_char_p]
SayHello.restype = None
SayHello(ctypes.c_char_p( b "hello" ))
```
我们首先通过ctypes.CDLL加载动态库到libso,并通过libso.SayHello来获取SayHello函数。获取到SayHello函数之后设置函数的输入参数为一个C语言类型的字符串,该函数没有返回值。然后我们通过 `ctypes.c_char_p(b"hello")` 将Python字节串转为C语言格式的字符串作为参数调用SayHello。如果一切正常的话就可以输出字符串了。
从这个例子可以看出,给予ctypes构造Python扩展模块非常简单,本质上只是在构建一个纯C语言规格的动态库。比较复杂的部分在ctypes的具体使用,关于ctypes的具体细节就不详细展开的,用户可以自行参考Python自带的官方文档。
## 基于Python C接口创建
在前面的例子中,通过ctypes创建的模块必须要用Python再包装一层,否则就要直接面对C语言风格的接口。如果基于基于Python C接口,我们可以完全再Go和C语言层面创建灵活强大的模块,重点是不再需要在Python中重新包装。
基于Python C接口创建模块和使用C语言的静态库的流程类似:
```go
package main
/*
// macOS:
#cgo darwin pkg-config: python3
// linux
#cgo linux pkg-config: python3
// windows
// should generate libpython3.a from python3.lib
#define Py_LIMITED_API
#include <Python.h>
extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject *, PyObject *);
static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
return PyArg_ParseTuple(arg, "ii", a, b);
}
static PyObject* cgo_PyInit_gopkg(void) {
static PyMethodDef methods[] = {
{"sum", Py_gopkg_sum, METH_VARARGS, "Add two numbers."},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT, "gopkg", NULL, -1, methods,
};
return PyModule_Create(&module);
}
*/
import "C"
func main() {}
//export PyInit_gopkg
func PyInit_gopkg() *C.PyObject {
return C.cgo_PyInit_gopkg()
}
//export Py_gopkg_sum
func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject {
var a, b C.int
if C.cgo_PyArg_ParseTuple_ii(args, &a, &b) == 0 {
return nil
}
return C.PyLong_FromLong(C.long(a + b))
}
```
因为Python的链接参数要复杂了很多,我们借助pkg-config工具来获取编译参数和链接参数。然后我们在Go语言中分别导出了PyInit_gopkg和Py_gopkg_sum函数,其中PyInit_gopkg函数用于初始化名为gopkg的Python模块,而Py_gopkg_sum函数则是模块中sum方法的实现。
因此PyArg_ParseTuple是可变参数类型,CGO中无法使用可变参数的C函数,因此我们通过增加一个cgo_PyArg_ParseTuple_ii辅助函数小消除可变参数的影响。同样,模块的方法列表必须在C语言内存空间创建,因为CGO是禁止将Go语言内存直接返回到C语言空间的。
然后通过以下命令创建gopkg.so动态库:
```
go build -buildmode=c-shared -o gopkg.so main.go
```
这里需要注意几个出现gopkg名字的地方。gopkg是我们创建的Python模块的名字,因此它对应一个gopkg.so动态库。再gopkg.so动态库中必须有一个PyInit_gopkg函数,该函数是模块的初始化函数。在PyInit_gopkg函数初始化模块时,同样需要指定模块的名字时gopkg。模块中的方法函数是通过函数指针访问,具体的名字没有影响。
### macOS环境构建
因为在macOS中,pkg-config不支持Python3版本。不过macOS有一个python3-config的命令可以实现pkg-config类似的功能。不过python3-config生成的编译参数无法直接用于CGO编译选项(因为GCC不能识别部分参数会导致错误构建)。
我们在python3-config的基础只是又包装了一个工具,在通过python3-config获取到编译参数之后将GCC不支持的参数剔除掉。
创建py3-config.go文件:
```go
func main() {
for _, s := range os.Args {
if s == "--cflags" {
out, _ := exec.Command(
"python3-config" , "--cflags",
).CombinedOutput()
out = bytes.Replace(out, []byte( "-arch" ), [] byte {}, - 1 )
out = bytes.Replace(out, []byte( "i386" ), [] byte {}, - 1 )
out = bytes.Replace(out, []byte( "x86_64" ), [] byte {}, - 1 )
fmt.Print(string(out))
return
}
if s == "--libs" {
out, _ := exec.Command(
"python3-config" , "--ldflags",
).CombinedOutput()
fmt.Print(string(out))
return
}
}
}
```
cgo中的pkg-config只需要两个参数 `--cflags` 和 `--libs` 。其中 `--libs` 选项的输出我们采用的是 `python3-config --ldflags` 的输出,因为 `--libs` 选项没有包含库的检索路径,而 `--ldflags` 选项则是在指定链接库参数的基础上增加了库的检索路径。
基于py3-config.go可以创建一个py3-config命令。然后通过PKG_CONFIG环境变量将cgo使用的pkg-config命令指定为我们订制的命令:
```
PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go
```
对于不支持pkg-config的平台我们都可以基于类似的方法处理。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端模块化架构设计与实现(二|模块接口设计)
- 【Node】详解模块的实现过程
- vue模块拖拽实现
- NodeJS 模块化的简易实现(commonJS)
- 从module的简单实现到模块化
- redis网络通信模块设计与实现分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Algorithmic Beauty of Plants
Przemyslaw Prusinkiewicz、Aristid Lindenmayer / Springer / 1996-4-18 / USD 99.00
Now available in an affordable softcover edition, this classic in Springer's acclaimed Virtual Laboratory series is the first comprehensive account of the computer simulation of plant development. 150......一起来看看 《The Algorithmic Beauty of Plants》 这本书的介绍吧!