go编写python拓展模块(python如何调用go语言的模块)

news/2024/10/17 22:22:39/

文章目录

  • go编写python模块(go 语言开发 Python 扩展)
    • 1. 什么是python拓展模块
    • 2. go 语言开发 Python 扩展思路
    • 3. go编写python2拓展模块 示例
    • 4. go编写python3拓展模块 示例
      • 代码优化
    • 5. python拓展模块什么时候加载
      • 查找拓展模块so文件位置的顺序
    • 6. go和c数据结构转化对应关系
    • 7. python C 拓展模块常用api
    • 8. python调用so go函数原理

go编写python模块(go 语言开发 Python 扩展)

1. 什么是python拓展模块

学习go语言编写Python扩展,可以参考官方文档:

  • https://golang.org/cmd/cgo/
  • https://docs.python.org/3/c-api/index.html

Python 为主导的项目中引入 Go 有以下几种方式:

  • 将 Go 源文件编译成动态库,然后直接通过 Python 的 ctypes 模块调用
  • 将 Go 源文件编译成动态库或者静态库,再结合 Cython 生成对应的 Python 扩展模块,然后直接 import 即可
  • 将 Go 源文件直接编译成 Python 扩展模块,当然这要求在使用 CGO 的时候需要遵循 Python 提供的 C API

Python拓展模块是用其它语言(通常是C/C++)编写,并可以在Python中导入和使用的模块。

2. go 语言开发 Python 扩展思路

拓展模块的开发步骤是:

  1. 用C/C++编写模块内容,并导出Python可调用的函数。
  2. 编写模块初始化函数,如:
PyMODINIT_FUNC PyInit_example(void) 
{// ...
}
  1. 使用Python C API定义模块的方法表、类型对象等。
    // example.c
PyMODINIT_FUNC PyInit_example(void) 
{static PyMethodDef methods[] = {{"add",  add, METH_VARARGS, "Add two numbers"},{NULL, NULL, 0, NULL}};static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,"example",      /* module name */ NULL,           /* docstring */-1,             /* size of per-interpreter state */ methods };return PyModule_Create(&moduledef);
}static PyObject* add(PyObject* self, PyObject* args) 
{int a, b;if (!PyArg_ParseTuple(args, "ii", &a, &b)) {return NULL;}return PyLong_FromLong(a + b);
}
  1. 编译成动态库(.so文件或.pyd文件)。
  2. 在Python中导入和使用这个模块。
import exampleexample.add(1, 2)  # Returns 3

调用add方法,它实际上会执行C代码中的add函数。

使用go原理类似:

  1. 编写go源代码,使用cgo编译生成共享库。cgo可以调用C语言的函数,所以go代码可以调用Python C API与Python进行交互。
  2. 导入C包,用于cgo调用Python C API

3. go编写python2拓展模块 示例

使用python.go和foo.go实现的简单Python扩展示例:

python.go:

package pymodule/*
#include <Python.h>
*/
import "C"
import "unsafe"//export InitModule
func InitModule() {C.Py_InitModule("pymodule", Methods)
}var Methods = []*C.PyMethodDef{{"say_hello", (C.PyCFunction)(C.PyCFunctionWithKeywords)(C.PyCFunctionCast)(unsafe.Pointer(C.SayHello)), METH_O, ""},
}

普通的 Go 只是多了一句 import “C”,除此之外没有任何和 CGO 相关的代码,也没有调用 CGO 的相关函数。但是由于这个 import,会使得 go build 命令在编译和链接阶段启动 gcc 编译器。

C 代码要通过注释的形式写在 import “C” 这行语句上方(中间不可以有空格,这是规定)。而一旦导入,就可以通过 C 这个名字空间进行调用,比如这里的 C.puts、C.CString 等等。

注意: import “C”,它不是导入一个名为 C 的包,我们可以将其理解为一个名字空间,C 语言的所有类型、函数等等都可以通过这个名字空间去调用。

编译之前,我们需要定义一些环境变量,让编译器知道在哪找Python.h
当使用 cgo 编译 go 代码生成共享库时,需要找到 Python 的头文件、库文件和 pkg-config 信息。通过设置这三个环境变量,go 的 cgo 可以找到 Python 2 的相关文件,完成 Python 扩展的编译。

如果不设置这些环境变量,cgo 在编译时很可能找不到 Python 的依赖,导致编译失败。

  1. PKG_CONFIG_PATH: 指定 pkg-config 工具搜索 *.pc 文件的路径。这里指定了 Python 的 pkg-config 文件路径。
  2. LD_LIBRARY_PATH: 指定动态链接库搜索路径。这里指定了 Python 的库文件路径。
  3. C_INCLUDE_PATH: 指定 C/C++ 头文件搜索路径。这里添加了 Python的 include 目录。

下面配置很重要:

export PKG_CONFIG_PATH=/home/test/env/python2/lib/pkgconfig/
export LD_LIBRARY_PATH=/home/test/env/python2/lib/export C_INCLUDE_PATH=/home/test/env/python2/include/python2.7

foo.go:

package pymodule//export SayHello
func SayHello(name *C.char) *C.char {return C.CString("Hello, " + C.GoString(name) + "!") 
}

编译

go build -buildmode=c-shared -ldflags '-s -w' -o pymodule.so python.go  foo.go

在Python中使用:

import pymodulepymodule.say_hello('John')  # Prints "Hello, John!"
  1. 在python.go中定义了InitModule函数来初始化模块,并定义了方法列表Methods
  2. 在foo.go中定义并导出SayHello函数
  3. 编译两个文件生成共享库pymodule.so
  4. 在Python中导入pymodule模块,调用say_hello方法
  5. say_hello方法会调用foo.go中的SayHello函数
  6. SayHello函数接收来自Python的name,并返回拼接的问候语
    所以,这个示例通过cgo实现了一个简单的Python扩展,导出一个say_hello方法,用于向指定的人问好。

4. go编写python3拓展模块 示例

Python 2和Python 3的C API在许多方面都发生了变化,导致同样的C代码在Python 2和3环境下编译结果不同。

Python 2和Python 3的一些主要C API变化包括:

  1. Py_InitModule被PyModule_Create替代
  2. PyArg_ParseTuple的格式字符串发生变化。
  3. Py_BuildValue的格式字符串发生变化。
  4. 其他许多C API也发生较大变化。
package main/*
#cgo pkg-config: python3 
#define Py_LIMITED_API
#include <Python.h>static PyObject *sayHello(PyObject *self, PyObject *args) {return PyUnicode_FromString("Hello from C!");
}static PyMethodDef FooMethods[] = {{"sayHello",  sayHello, METH_NOARGS, "Say hello"},{NULL, NULL, 0, NULL}  
};static struct PyModuleDef hellomodule = {PyModuleDef_HEAD_INIT,"hello",  NULL,  -1, NULL
};PyMODINIT_FUNC PyInit_hello()
{PyModuleDef_Init(&hellomodule);hellomodule.m_methods = FooMethods;PyObject *m = PyModule_Create(&hellomodule);return m;
}
*/
import "C"func main() {}

注释这部分代码实际上是C语言,只不过它调用了Go导出的sayHello函数。
编译时cgo会解析C注释,并使用其中的实现生成Python扩展。

  1. 定义sayHello方法,返回"Hello from C!"。
  2. 定义FooMethods方法数组,指定sayHello方法。
  3. 定义hellomodule模块。
  4. 实现PyInit_hello初始化函数,设置m_methods并创建模块。

这段代码使用了Python C API中的几个重要函数:

  1. PyUnicode_FromString:构造一个Python字符串对象。我们使用它来构造"Hello from C!"返回值。
  2. PyArg_ParseTuple:解析Python函数的参数。这里我们没有使用它,因为sayHello是无参数方法。
  3. Py_BuildValue:构造Python返回值。我们也没有直接使用它,而是使用PyUnicode_FromString构造字符串对象。
  4. PyMethodDef:定义方法信息,我们使用它来定义sayHello方法信息。
  5. PyModuleDef:定义模块信息,我们使用它来定义hello模块信息
  6. PyModuleDef_Init:初始化PyModuleDef实例。
  7. PyModule_Create:创建一个模块,传入PyModuleDef实例。
  8. PyMODINIT_FUNC:定义模块初始化函数返回类型。我们使用它来定义PyInit_hello的返回类型。

在Python中:

import hellohello.sayHello()  # Prints "Hello from C!"

代码优化

将C代码写在注释中有一定的局限性:

  1. 只能写很简短的代码,不利于开发较复杂的扩展。
  2. 没有自动完成功能,编写不方便。
  3. 调试困难,不能使用Go的调试工具。

5. python拓展模块什么时候加载

Python中,扩展模块是在导入时加载的。具体 load 的过程是:

  1. 当我们在 Python 中导入一个扩展模块时,Python 会搜索名为 module.so(Linux) 或 module.pyd(Windows)的文件。
  2. 如果找到该文件,Python 会加载它,并执行里面的初始化函数。对于我们的例子,这个函数是 PyInit_hello()。
  3. 这个初始化函数会返回一个模块对象。Python 使用这个对象来初始化一个模块,并将它插入 sys.modules,这样在后续的导入中可以重用该模块。
  4. Python 中可以使用该模块,并调用它暴露的方法。

查找拓展模块so文件位置的顺序

当在 Python 中导入一个扩展模块时,Python 会按照以下顺序搜索名为 module.so 的文件:

  1. 当前目录。Python 会先搜索当前路径下的 module.so 文件。
  2. PYTHONPATH 路径。PYTHONPATH 环境变量指定的路径下搜索 module.so 文件。
  3. 构建路径。如果是从源码编译的 Python,会搜索构建路径下的 module.so 文件。
  4. 动态链接库路径。搜索系统的动态链接库路径,如 Linux 下的 /usr/lib 等。
  5. 创建 module.so 软链接。如果上述路径下存在 module.so 的软链接,Python 也会进行搜索。
  6. 报 ImportError。如果在上述所有路径下都没有找到 module.so 文件,Python 会抛出 ImportError。
    所以我们可以通过:
  7. 将 module.so 文件置于当前路径或者 PYTHONPATH 下。
  8. 创建 module.so 到上述路径的软链接。
  9. 安装 module.so 文件到系统的动态链接库路径下。
  10. 在源码编译 Python 时指定 module.so 路径。
    来确保 Python 可以成功导入该扩展模块。

6. go和c数据结构转化对应关系

  1. 基本类型:
    | Go | C |
    | ---------- | -------------- |
    | byte | char |
    | rune | wchar_t |
    | int | int |
    | uint | unsigned int |
    | int32 | int32_t |
    | uint32 | uint32_t |
    | int64 | int64_t |
    | uint64 | uint64_t |
    | float32 | float |
    | float64 | double |
    | complex64 | float complex |
    | complex128 | double complex |

  2. 字符串:
    Go中的字符串被转化为C中的char*。在C中操作字符串时需要注意长度和空终止等。

  3. 切片:
    Go中的切片被转化为C中的结构体:
    c
    struct {
    void *data;
    int len;
    int cap;
    }
    data指向切片的底层数组,len是切片的长度,cap是容量。

  4. 结构体:
    Go中的结构体会被“扁平化”转化为C,丢失结构信息。取而代之的是对应的C类型的变量。如果要在C中重构这个结构,需要定义与之对应的C结构体。

  5. 接口:
    Go中的接口在转化为C后会丢失,需要通过其他机制来表示,如函数指针等。

  6. 通道:
    Go中的通道无法直接在C中表示。可以在Go中以通道来发送/接受数据,在C中以其他方式来模拟通道的效果。

  7. 函数:
    在C/Go之间转化时,需要使用//export和cgo的调用约定来定义可以相互调用的函数。函数的参数与返回值也需要满足两种语言的类型兼容要求。

7. python C 拓展模块常用api

Python C API的官方文档地址:https://docs.python.org/3/c-api/

Python语言本身是用C语言实现的。而Python C API则是在C语言中嵌入和扩展Python的接口。

Python C API包含一组在C语言中使用的宏、函数和类型等,用于创建Python的拓展模块。借助该API,我们可以在C语言中嵌入Python的解释器,调用Python的函数与对象,实现C语言与Python之间的数据交换与操作。

Python C API用于创建拓展模块,它包含许多函数,常用的一些如下:

  1. Py_Initialize(): 初始化Python解释器,必须调用
    当使用cgo调用Python C API时,cgo会自动在背后调用Py_Initialize()进行初始化。所以在我们的C代码中不需要显式调用。
    如果不使用cgo,而是直接使用Python C API,那么需要在代码开始处调用Py_Initialize(),例如:
int main() {Py_Initialize();// ...Py_Finalize();
}

如果不使用cgo,需要在开始和结束处分别调用Py_Initialize()和Py_Finalize()。
Py_Initialize()会初始化Python解释器,设置信号处理函数等。而Py_Finalize()会正确关闭Python解释器,释放资源。

cgo是Go语言调用C语言函数的接口。它允许我们在Go源码中直接嵌入C代码,并在两种语言之间传递数据。

cgo会在背后自动调用一些初始化函数,如Py_Initialize(),这是因为:
当我们在Go代码中调用Python C API时,Python的解释器需要被初始化,否则无法工作。所以cgo会在我们的C代码被执行前自动调用Py_Initialize()进行初始化。

cgo会自动在开始和结束调用C API时分别调用Py_Initialize()和Py_Finalize()。我们不需要在C代码中显式调用。
但是我们仍需要调用Py_Finalize()确保解释器正确退出。cgo只会在程序退出时调用Py_Finalize(),而不会在每个C代码执行结束时调用。

  1. Py_Finalize(): 退出Python解释器,可选调用。

  2. Py_BuildValue(): 创建Python对象,根据格式字符串决定对象类型。如"i"->int,“s”->str等。

  3. PyArg_ParseTuple(): 从Python传递过来的参数中提取数据,填充C变量。如"si"可以提取int和str。

  4. PyModule_Create(): 创建Python模块。 python3 使用这个!

  5. PyModule_AddObject(): 将对象添加到模块中。

  6. PyObject_CallObject(): 调用一个Python对象(如函数)。

  7. PyCallable_Check(): 检查一个对象是否可调用。

  8. PyErr_Format(): 设置一个异常。

  9. Py_None: None对象,用于返回None值。

  10. PyTuple_Pack(): 创建元组对象。

  11. PyDict_SetItemString(): 将一个值加入字典。

  12. PyString_FromString(): 创建字符串对象。

  13. PyInt_FromLong(): 从long创建int对象。

  14. PyModule_GetDict(): 获取模块的字典。

8. python调用so go函数原理

我们的so模块是通过cgo生成的,它依赖Go语言代码。所以Python调用so模块时,实际上会触发Go语言代码的执行。

具体过程是:

  1. 我们使用Go语言编写cgo代码,如sayHello函数,然后编译生成so模块。
  2. 在Python中导入这个so模块,并调用其函数,如sayHello。
  3. Python调用sayHello时,会触发so模块中cgo生成的包装函数执行。
  4. 这个包装函数又会调用我们编写的sayHello Go函数。所以实际执行是在Go语言层。
  5. 我们的sayHello函数运行在一个goroutine中,这个goroutine就是Go进程。
  6. Go进程会随着我们的Go程序一直运行,直到程序退出。
  7. 我们需要在Go程序退出前调用C.Py_Finalize()关闭解释器。

加入我们的go的main函数是空的, func main() {},理论调用sayHello整个生命周期就结束了,还需要调C.Py_Finalize()关闭解释器吗?

由于我们的main函数是空的,理论上调用sayHello函数后,相应的Go进程应该就结束了。

但是,事实并非如此。这是因为:

  1. 假如我们在sayHello函数中,我们启动了一个goroutine执行时间consuming的操作(如睡眠)。
  2. 由于sayHello函数是cgo导出的,会转换为C语言风格。在C语言中不存在goroutine的概念。
  3. 所以,尽管我们在sayHello中启动了goroutine,但在cgo转换为C函数后,这个语义会丢失。
  4. 结果是,goroutine实际上启动了一个独立的Go进程,但sayHello函数无法等待它结束就返回了。
  5. 这样,调用sayHello后的Python程序会结束,但之前启动的Go进程却在默默运行,导致资源泄漏。

总结:所以这种情况,还是看你代码写的有没有问题。

为什么我们即使在示例代码的main函数中调用C.Py_Finalize()也无法解决资源泄露?

在Go的main函数中调用C.Py_Finalize(),理论上它应该可以结束sayHello中启动的goroutine。

但是,当Python加载我们的so库并调用sayHello函数时,会重新触发Go代码执行,启动一个全新的Go进程。而此时Go的main函数已经退出,无法再调用C.Py_Finalize()。所以新启动的Go进程会一直运行,导致资源泄漏。

总结:main函数只在编译so库时执行一次,用于初始化我们的Go代码。

main函数只在编译so库时执行一次,用于初始化我们的Go代码。
当Python实际调用sayHello函数时,会重新触发我们的Go代码执行,启动一个全新的Go进程。
此时,这个新进程与我们编译时执行的main函数毫无关系。main函数已经退出,无法再执行任何操作。

所以,当Python调用sayHello函数时:

  1. 我们的Go代码会重新执行,但与编译时执行的main函数无关。
  2. 这个新进程无法调用main函数中定义的任何函数或变量。main函数所在的上下文已经消失。
  3. 只有全局变量的状态会保留在so库中,新进程可以访问。但main函数本身不会再执行。
  4. 所以,在这个新进程中,无法再调用C.Py_Finalize()。它必须在Python程序的入口函数中调用,与Go代码无关。

要彻底理解这个问题,需要明确:
5. 生成so库的过程与执行go代码的关系。编译时会执行一次main函数,用于初始化
6. Python加载so库与调用cgo函数的机制。会重新触发go代码执行,启动一个全新的进程。
7. 进程的特征与上下文的概念。每个进程都有自己的上下文,无法共享main函数的上下文。
8. Cgo函数的执行过程。真正执行cgo函数的是一个全新的Go进程,与编译时无关。

总结:要避免资源泄漏,唯一的方法是:确保sayHello函数自身不会产生资源泄漏。

  1. sayHello函数自身不产生资源泄漏,及时回收所有资源。
  2. 在Python代码中调用C.Py_Finalize(),在程序结束前关闭解释器。

http://www.ppmy.cn/news/239617.html

相关文章

身为大学生,你不会还不知道有这些学生福利吧!!!!

本文介绍的是利用学生身份可以享受到的相关学生优惠权益&#xff0c;但也希望各位享受权利的同时不要忘记自己的义务&#xff0c;不要售卖、转手自己的学生优惠资格&#xff0c;使得其他同学无法受益。 前言 高考已经过去&#xff0c;我们也将迎来不同于以往的大学生活&#x…

库克回应 iPhone 11 系列不支持 5G;哈啰 App 被下架;Flutter 1.9 稳定版发布 | 极客头条...

快来收听极客头条音频版吧&#xff0c;智能播报由标贝科技提供技术支持。 「CSDN 极客头条」&#xff0c;是从 CSDN 网站延伸至官方微信公众号的特别栏目&#xff0c;专注于一天业界事报道。风里雨里&#xff0c;我们将每天为朋友们&#xff0c;播报最新鲜有料的新闻资讯&…

9月12日科技资讯|库克回应 iPhone 11 系列不支持 5G;Flutter 1.9 稳定版发布

「CSDN 极客头条」&#xff0c;是从 CSDN 网站延伸至官方微信公众号的特别栏目&#xff0c;专注于一天业界事报道。风里雨里&#xff0c;我们将每天为朋友们&#xff0c;播报最新鲜有料的新闻资讯&#xff0c;让所有技术人&#xff0c;时刻紧跟业界潮流。 整理 | 胡巍巍 责编 …

国内外有哪些有前景的 AR VR公司?

http://www.zhihu.com/question/28446842 作者&#xff1a;胡痴儿2.0 链接&#xff1a;http://www.zhihu.com/question/28446842/answer/69752272 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 AR AR显示 【Microsof…

ROS机器人程序设计(原书第2版).

机器人设计与制作系列 ROS机器人程序设计 (原书第2版) Learning ROS for Robotics Programming,Second Edition 恩里克费尔南德斯(Enrique Fernndez) 路易斯桑切斯克雷斯波(Luis Snchez Crespo) 阿尼尔马哈塔尼(Anil Mahtani) 亚伦马丁内斯(Aaron Martinez) 著 刘锦…

PHP 7.2 Beta 的 Benchmarks 测试,PHP 仍然越来越快;TypeScript 2.4.2 发布;

&#xff08;点击上方蓝字&#xff0c;快速关注我们&#xff09; 综合&#xff1a;开源中国、ZDNet、腾讯科技、快科技、solidot、cnBeta、IT之家等 0、苹果被打脸 黑客分分钟越狱 Apple Watch 在 DEFCON 安全大会上&#xff0c;一名叫 Max Bazaliy 的黑客详细演示了完全越狱 A…

【产品经理】从产品经理的技术理解力看产品需求流程

一、写在前面 鹅厂对产品经理的能力项要求中有一条重要考量&#xff0c;叫做技术理解力。所谓的技术理解力&#xff0c;不是让产品经理学看代码&#xff0c;而是在沟通、需求、及项目推进时&#xff0c;思考方式与技术人员保持基本同步。这应该怎么理解呢&#xff1f;就让我们…

行业 | 区块链技术演进简史:人才都去了哪儿?最受开发欢迎的编程语言是?(下

读懂智能&未来 首页专栏专题特辑 公开课AI慕课学院爱搞机 业界 人工智能 智能驾驶 AI Fintech&区块链 未来医疗 网络安全 AR/VR 机器人 开发者 智能硬件 物联网 GAIR Fintech&区块链 正文 0 行业 | 区块链技术演进简史&#xff1a;人才都去了哪儿&#xff1f;最受开…