python C 扩展(转载)

转载: https://blog.csdn.net/yueguanghaidao/article/details/11708417

 

我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等。强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)的基础知识。

 

一:扩展中的异常处理

    高级语言如C++,Java等都有完善的异常控制,Python也不例外。但与C++不同的是,写C++你可以完全抛弃异常处理,但Python中基本是不可能的。记得Google的C++编码规范中明确指出,他们是不允许使用异常的,因为会打乱控制流,导致调试等复杂度增加,有兴趣的童鞋可以看看。但Python中你是避免不了的,很多内置函数和模块都大量的使用了异常。所以为了我们编写的模块更加Pythonic,异常处理免不了。

下面一段代码是在上一篇文章中的增强版,主要就是在异常处理方面。

 

[cpp] view plain copy
 
  1. #include <Python.h>  
  2. static PyObject *SpamError;  
  3. static PyObject *SpamIoError;  
  4.   
  5. static PyObject *spam_system(PyObject *self, PyObject *args)  
  6. {  
  7.     const char *command;  
  8.     int sts;  
  9.   
  10.     if (!PyArg_ParseTuple(args, "s", &command))  
  11.         return NULL;  
  12.     sts = system(command);  
  13.     if(sts<0){ //系统调用返回-1,失败;返回0,代表执行成功;返回其它的,也代表错误,如没有这个命令等  
  14.         PyErr_SetString(SpamError,"system call failed");  
  15.         return NULL;  
  16.     }  
  17.     else if(sts>0){  
  18.         PyErr_SetString(SpamIoError,"Io failed");  
  19.         return NULL;  
  20.     }  
  21.     return PyLong_FromLong(sts);  
  22. }  
  23.   
  24. static PyMethodDef SpamMethods[] = {  
  25.     {"system",  spam_system, METH_VARARGS,"Execute a shell command."},  
  26.     {NULL, NULL, 0, NULL}  
  27. };  
  28.   
  29. PyMODINIT_FUNC initspam(void)  
  30. {  
  31.     PyObject *m;  
  32.     m=Py_InitModule("spam", SpamMethods);  
  33.     if (m==NULL)  
  34.         return;  
  35.     SpamError=PyErr_NewException("spam.error",NULL,NULL);  
  36.     SpamIoError=PyErr_NewException("spam.IoError",NULL,NULL);  
  37.     Py_INCREF(SpamError);  
  38.     Py_INCREF(SpamIoError);  
  39.     PyModule_AddObject(m,"error",SpamError);  
  40.     PyModule_AddObject(m,"IoError",SpamIoError);  
  41.   
  42. }  

 

我们写来看一下使用:

   通过dir(),我们发现新增加了error和IoError,我们看看这到底是什么?

   哦,原来spam.error就是一个异常类,是通过代码39行加入:PyModule_AddObject的函数原形,int PyModule_AddObject(PyObject *module, const char *name, PyObject *value),其中module就是Py_InitModule()返回的对象。代码39行:PyModule_AddObject(m,"error",SpamError),含义就是将SpamError这个类加入m这个模块中,并简记为“error”。而SpamError就是一个异常类,是通过35行的SpamError=PyErr_NewException("spam.error",NULL,NULL)创建的,PyObject* PyErr_NewException(char *name, PyObject *base, PyObject *dict)返回一个新创建的异常类,SpamError是程序开头定义的一个静态变量,到这里我们都知道了异常类如何定义了,那如何使用呢?

   代码14行,当系统调用失败,通过PyErr_SetString(SpamError,"system call failed")设置异常,一般都是使用void PyErr_SetString(PyObject *type, const char *message)来设置异常,代码很简洁,下面通过实例看看如何使用。

  我们调用了一个不存在的命令,很明显抛出的是IoError,额突然发现名字起的不好,应该为NotFound异常更加贴切。

  这里需要注意37行的Py_INCREF(SpamError),由于异常可以由外部代码删除,这样将导致SpamError成为悬垂指针(也就是SpamError指向的地址被释放,但SpamError还是指向原来的地址),所以将SpamError的引用加1,这样异常类就不会被释放。

   下面是几点扩展知识点:

1,如果要忽略函数抛出的异常,可以用PyErr_Clear()。因为异常只有传递到Python解释器时才起作用,所以在C API层次可以清除。

2,如果是申请内存函数(malloc等)失败,要设置异常,需要设置PyErr_NoMemory(),并需要将异常指示器返回,简单来说就是这样:return PyErr_NoMemory();

主要是所有的对象创建函数都是这么干的,所以我们要遵守规矩。

   下面给一个demo程序,也就是上面代码的演化版,增加了异常清除,给模块增加变量,如模块版本信息,作者等。

 

[cpp] view plain copy
 
  1. #include <Python.h>  
  2. static PyObject *SpamError;  
  3. static PyObject *SpamIoError;  
  4.   
  5. void Pysystem(const char *command)  
  6. {  
  7.     int sts;  
  8.     sts = system(command);  
  9.     if(sts<0){ //系统调用返回-1,失败;返回0,代表执行成功;返回其它的,也代表错误,如没有这个命令等  
  10.         PyErr_SetString(SpamError,"system call failed");  
  11.     }  
  12.     else if(sts>0){  
  13.         PyErr_SetString(SpamIoError,"Io failed");  
  14.     }  
  15. }  
  16.   
  17. static PyObject *spam_system(PyObject *self, PyObject *args)  
  18. {  
  19.     const char *command;  
  20.     if (!PyArg_ParseTuple(args, "s", &command))  
  21.         return NULL;  
  22.     Pysystem(command);  
  23.     PyErr_Clear(); //清除异常  
  24.     return PyLong_FromLong(0);  
  25. }  
  26.   
  27. static PyMethodDef SpamMethods[] = {  
  28.     {"system",  spam_system, METH_VARARGS,"Execute a shell command."},  
  29.     {NULL, NULL, 0, NULL}  
  30. };  
  31.   
  32. PyMODINIT_FUNC initspam(void)  
  33. {  
  34.     PyObject *m;  
  35.     PyObject *mversion =PyString_FromString("1.0.0");  
  36.     PyObject *mauthor =PyString_FromString("skycrab");  
  37.     m=Py_InitModule("spam", SpamMethods);  
  38.     if (m==NULL)  
  39.         return;  
  40.   
  41.     SpamError=PyErr_NewException("spam.errorClass",NULL,NULL);  
  42.     SpamIoError=PyErr_NewException("spam.IoErrorClass",NULL,NULL);  
  43.     Py_INCREF(SpamError);  
  44.     Py_INCREF(SpamIoError);  
  45.     PyModule_AddObject(m,"error",SpamError);  
  46.     PyModule_AddObject(m,"IoError",SpamIoError);  
  47.     PyModule_AddObject(m,"version",mversion);  
  48.     PyModule_AddObject(m,"author",mauthor);  
  49.   
  50. }  

代码很简单,看交互效果。

 

   从上面我们可以看出,的确没有抛出异常,而且返回值也的确是0。

 

二,引用计数

  Python中使用了引用计数(为主)来解决垃圾回收,想了解Python如何进行垃圾回收可以看Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解。那么在C API层次如何控制引用的呢?

  常用的有4个C API,

 

[cpp] view plain copy
 
  1. void Py_INCREF(PyObject *o)        //  增加引用,o不可以为NULL  
  2. void Py_XINCREF(PyObject *o)     //  增加引用,o可以为NULL  
  3. void Py_DECREF(PyObject *o)     //  减少引用,o不可以为NULL  
  4. void Py_XDECREF(PyObject *o)  //  减少引用,o可以为NULL  
  在Python C API中,函数返回值一般都是返回引用,但分为2种引用,Borrowed reference和New reference(不知道翻译成什么比较合适)

 

  我们先来看两个API声明:

 

[cpp] view plain copy
 
  1. PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)  
  2.      Return value: Borrowed reference.  
  3.   
  4. PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)  
  5.      Return value: New reference.  
   我们平常使用较多的是New reference,也就是能完全拥有的对象。所谓完全拥有,简单点说就是引用计数已经加1。

 

而Borrowed reference其实只是一个指针,引用计数并没有增加,其实就是Python层次的Weakref(弱引用) ,所以使用时,你必须保证指向对象没有被释放。

   我们来看下面一段代码:

 

[cpp] view plain copy
 
  1. void  
  2. bug(PyObject *list)  
  3. {  
  4.     PyObject *item = PyList_GetItem(list, 0);  
  5.   
  6.     PyList_SetItem(list, 1, PyInt_FromLong(0L));  
  7.     PyObject_Print(item, stdout, 0); /* BUG! */  
  8. }  
  上面完成功能就是从列表list中取出索引为0的对象item,然后将list[1]设置为0,最后打印出item。

 

  应该没有问题吧?每当底气不足时,就是存在问题的时候。试想如下场景,当我设置list[1]时肯定要先释放list[1](del list[1]),如果list[1]对象定义了__del__方法,而在__del__方法中有可能del list[o],而PyList_GetItem返回的是Borrowed reference,这将导致item指针无效。

  那如本避免呢,很简单,在设置之前手动增加一次引用,最后再减少一次就OK了,代码如下:

 

[cpp] view plain copy
 
  1. void  
  2. no_bug(PyObject *list)  
  3. {  
  4.     PyObject *item = PyList_GetItem(list, 0);  
  5.   
  6.     Py_INCREF(item);  
  7.     PyList_SetItem(list, 1, PyInt_FromLong(0L));  
  8.     PyObject_Print(item, stdout, 0);  
  9.     Py_DECREF(item);  
  10. }  


 

 
posted @ 2018-05-22 18:09  Andy.gbhu  阅读(529)  评论(0)    收藏  举报