标题:给Python的C模块增加一个“析构函数” 出处:Felix021 时间:Sun, 20 Jan 2013 00:50:54 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?2103 内容: 这周写了个模块,让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模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进): PyRun_SimpleString( "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); 由于错误处理、引用计数的代码占了一大半,所以代码这么长…… 这种方法的好处是,它在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(); 也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制! 甚至在这后面还有一段注释: /* XXX Still allocated: - 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; } 我最终采用的解决方案是这个: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()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。 Generated by Bo-blog 2.1.0