Jan
20
给Python的C模块增加一个“析构函数”
这周写了个模块,让python可以跟后台的C服务通信了。
总体上来说给python写个模块还是比较容易的,比给php写模块要舒服多了,但是还是遇到一个问题:给php写模块的时候可以用MINIT完成初始化、MSHUTDOWN完成清理;给Python写模块,有对应的模块初始化函数,但是却没有对应的清理函数,实在是令人蛋疼。
需求其实很简单(应该也很普遍吧?),只是要在python退出之前执行一点代码,保证在初始化的时候分配的一些资源能够被释放;但是模块本身并没有提供这样的机制,只好想其他办法了。
最简单的是用C语言本身提供的 atexit ,不过这是在main()结束或者调用exit()时 - 也就是说,在整个C环境要结束了的情况下才会运行(但是在关闭所有打开的文件之前)。
虽然有效,但是由于它不是python提供的机制,多少让人有点不太放心,所以还是看看别人怎么用的吧。
Google搜了一下"python module destructor",stackoverflow上说的是,可以用python的 atexit 模块。这是一个纯python模块,源码可以在 /usr/lib/pythonX.X/atexit.py 看到,其实就是通过 atexit.register() 注册退出函数,并将 atexit._run_exitfuncs() 函数绑定到 sys.exitfunc 。sys.exitfunc会在Py_Finalize()这个函数中被调用(call_sys_exitfunc())。
在C模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进):
这大致相当于在python脚本里用eval执行了一段代码(没有指定globals、locals和compile_flag)。也可以用如下几乎等价的纯C代码
由于错误处理、引用计数的代码占了一大半,所以代码这么长……
这种方法的好处是,它在python运行环境结束之前执行注册的函数,所以注册的函数仍然可以使用绝大部分python提供的功能(你甚至可以再import新的模块,但是最好别用thread...)。然而atexit有一个蛋疼的缺陷:尽管python在sys模块的doc里写的是“Assigning to sys.exitfunc is deprecated; use the atexit module instead.”,但是sys.exitfunc仍然是任意python脚本都可以修改的!所以通过atexit.register()注册的函数并不能100%保证被运行。
再回过头来看Py_Finalize()函数,这里其实是有很大槽点的:它有一个长达十一行的"sundry finalizers"段!
也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制!
甚至在这后面还有一段注释:
再往下看,在Py_Finalize()的最后一行终于出现了本篇的另一个主角:call_ll_exitfuncs(); 这个函数的内容很简单:逐一执行 exitfuncs 这个数组里保存的函数。而 exitfuncs 这个数组的内容,则是由 Py_AtExit() 函数填进去的:
我最终采用的解决方案是这个:Py_AtExit()。
最后总结对比一下上述3种方案:
1. C语言的 atexit() : 使用链表保存注册的函数,只要内存够,数量没限制。在Python完全结束后执行。
2. Python的 atexit 模块:使用 list 保存注册的函数,在register的时候还可以带参数,在Python解释器仍然完整的情况下执行,一般来说很够用;但由于使用的sys.exitfunc可能会被其他脚本使用,并不能100%保证有效,故,有强迫症的慎用。或者应当在doc里明确说明。
3. Python C API的 Py_AtExit() :在Python环境几乎完全结束的时候被调用,最多只能注册32个函数(所以最好要检查返回值)。其实从流程上来说,在这之后马上就是main的return或者exit()函数调用,所以跟atexit基本上差不多。
p.s. 另一个尝试但是失败了的方案:建立一个新的type X_Type,在它的tp_dealloc里头写入清理代码,以X_Type建立一个新的class X,在模块的初始化函数中new一个X,撂着,并期望Py_Finalize()里的PyGC_Collect()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。
转载请注明出自 ,如是转载文则注明原出处,谢谢:)
RSS订阅地址: https://www.felix021.com/blog/feed.php 。
总体上来说给python写个模块还是比较容易的,比给php写模块要舒服多了,但是还是遇到一个问题:给php写模块的时候可以用MINIT完成初始化、MSHUTDOWN完成清理;给Python写模块,有对应的模块初始化函数,但是却没有对应的清理函数,实在是令人蛋疼。
需求其实很简单(应该也很普遍吧?),只是要在python退出之前执行一点代码,保证在初始化的时候分配的一些资源能够被释放;但是模块本身并没有提供这样的机制,只好想其他办法了。
最简单的是用C语言本身提供的 atexit ,不过这是在main()结束或者调用exit()时 - 也就是说,在整个C环境要结束了的情况下才会运行(但是在关闭所有打开的文件之前)。
虽然有效,但是由于它不是python提供的机制,多少让人有点不太放心,所以还是看看别人怎么用的吧。
Google搜了一下"python module destructor",stackoverflow上说的是,可以用python的 atexit 模块。这是一个纯python模块,源码可以在 /usr/lib/pythonX.X/atexit.py 看到,其实就是通过 atexit.register() 注册退出函数,并将 atexit._run_exitfuncs() 函数绑定到 sys.exitfunc 。sys.exitfunc会在Py_Finalize()这个函数中被调用(call_sys_exitfunc())。
在C模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进):
PyRun_SimpleString(
"import atexit\n"
"def __modname_clean():\n"
" import modname\n"
" modname.clean()\n"
"atexit.register(__modname_clean)\n");
"import atexit\n"
"def __modname_clean():\n"
" import modname\n"
" modname.clean()\n"
"atexit.register(__modname_clean)\n");
这大致相当于在python脚本里用eval执行了一段代码(没有指定globals、locals和compile_flag)。也可以用如下几乎等价的纯C代码
//注:m是在模块初始化函数中的定义的当前模块对象
PyObject *pClean = PyObject_GetAttrString(m, "clean");
if (pClean != NULL)
{
PyObject *pName = PyString_FromString("atexit");
PyObject *pMod = PyImport_Import(pName);
Py_DECREF(pName);
if (pMod != NULL)
{
PyObject *pFunc = PyObject_GetAttrString(pMod, "register");
if (pFunc && PyCallable_Check(pFunc))
{
PyObject *pArgs = Py_BuildValue("(O)", pClean);
PyObject *ret = PyObject_CallObject(pFunc, pArgs);
//if (ret == NULL) ; //sth went wrong
Py_XDECREF(pArgs);
Py_XDECREF(ret);
}
Py_XDECREF(pFunc);
}
Py_XDECREF(pMod);
}
Py_XDECREF(pClean);
PyObject *pClean = PyObject_GetAttrString(m, "clean");
if (pClean != NULL)
{
PyObject *pName = PyString_FromString("atexit");
PyObject *pMod = PyImport_Import(pName);
Py_DECREF(pName);
if (pMod != NULL)
{
PyObject *pFunc = PyObject_GetAttrString(pMod, "register");
if (pFunc && PyCallable_Check(pFunc))
{
PyObject *pArgs = Py_BuildValue("(O)", pClean);
PyObject *ret = PyObject_CallObject(pFunc, pArgs);
//if (ret == NULL) ; //sth went wrong
Py_XDECREF(pArgs);
Py_XDECREF(ret);
}
Py_XDECREF(pFunc);
}
Py_XDECREF(pMod);
}
Py_XDECREF(pClean);
由于错误处理、引用计数的代码占了一大半,所以代码这么长……
这种方法的好处是,它在python运行环境结束之前执行注册的函数,所以注册的函数仍然可以使用绝大部分python提供的功能(你甚至可以再import新的模块,但是最好别用thread...)。然而atexit有一个蛋疼的缺陷:尽管python在sys模块的doc里写的是“Assigning to sys.exitfunc is deprecated; use the atexit module instead.”,但是sys.exitfunc仍然是任意python脚本都可以修改的!所以通过atexit.register()注册的函数并不能100%保证被运行。
再回过头来看Py_Finalize()函数,这里其实是有很大槽点的:它有一个长达十一行的"sundry finalizers"段!
/* Sundry finalizers */
PyMethod_Fini();
PyFrame_Fini();
PyCFunction_Fini();
PyTuple_Fini();
PyList_Fini();
PySet_Fini();
PyString_Fini();
PyByteArray_Fini();
PyInt_Fini();
PyFloat_Fini();
PyDict_Fini();
PyMethod_Fini();
PyFrame_Fini();
PyCFunction_Fini();
PyTuple_Fini();
PyList_Fini();
PySet_Fini();
PyString_Fini();
PyByteArray_Fini();
PyInt_Fini();
PyFloat_Fini();
PyDict_Fini();
也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制!
甚至在这后面还有一段注释:
/* XXX Still allocated:
- various static ad-hoc pointers to interned strings
- int and float free list blocks
- whatever various modules and libraries allocate
*/
坑爹啊。。。- various static ad-hoc pointers to interned strings
- int and float free list blocks
- whatever various modules and libraries allocate
*/
再往下看,在Py_Finalize()的最后一行终于出现了本篇的另一个主角:call_ll_exitfuncs(); 这个函数的内容很简单:逐一执行 exitfuncs 这个数组里保存的函数。而 exitfuncs 这个数组的内容,则是由 Py_AtExit() 函数填进去的:
#define NEXITFUNCS 32 //另一个坑:最多只能注册32个exitfunc
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;
int Py_AtExit(void (*func)(void))
{
if (nexitfuncs >= NEXITFUNCS)
return -1;
exitfuncs[nexitfuncs++] = func;
return 0;
}
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;
int Py_AtExit(void (*func)(void))
{
if (nexitfuncs >= NEXITFUNCS)
return -1;
exitfuncs[nexitfuncs++] = func;
return 0;
}
我最终采用的解决方案是这个:Py_AtExit()。
最后总结对比一下上述3种方案:
1. C语言的 atexit() : 使用链表保存注册的函数,只要内存够,数量没限制。在Python完全结束后执行。
2. Python的 atexit 模块:使用 list 保存注册的函数,在register的时候还可以带参数,在Python解释器仍然完整的情况下执行,一般来说很够用;但由于使用的sys.exitfunc可能会被其他脚本使用,并不能100%保证有效,故,有强迫症的慎用。或者应当在doc里明确说明。
3. Python C API的 Py_AtExit() :在Python环境几乎完全结束的时候被调用,最多只能注册32个函数(所以最好要检查返回值)。其实从流程上来说,在这之后马上就是main的return或者exit()函数调用,所以跟atexit基本上差不多。
p.s. 另一个尝试但是失败了的方案:建立一个新的type X_Type,在它的tp_dealloc里头写入清理代码,以X_Type建立一个新的class X,在模块的初始化函数中new一个X,撂着,并期望Py_Finalize()里的PyGC_Collect()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。
欢迎扫码关注:
转载请注明出自 ,如是转载文则注明原出处,谢谢:)
RSS订阅地址: https://www.felix021.com/blog/feed.php 。