Chinaunix首页 | 论坛 | 博客
  • 博客访问: 774733
  • 博文数量: 239
  • 博客积分: 60
  • 博客等级: 民兵
  • 技术积分: 1045
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-22 18:25
文章分类

全部博文(239)

文章存档

2019年(9)

2018年(64)

2017年(2)

2016年(26)

2015年(30)

2014年(41)

2013年(65)

2012年(2)

分类: Python/Ruby

2016-09-12 11:26:17

(承接上篇)

六. C/C++ Extending标准库

场景。无可用Python模块,将C API整合到Python类型系统中,造福众人。

实现。

1. 假设已经写好C扩展(后面介绍实现),Python调用C扩展再次实现一遍同样的需求

点击(此处)折叠或打开(notify3.py)

  1. import datetime
  2. import inotify
  3. import sys

  4. MASK_EVENTS = [(k,v) for k,v in sorted(list(inotify.__dict__.items()))
  5.          if k.startswith('IN_') and \
  6.                  k not in ('IN_CLOEXEC', 'IN_NONBLOCK') and \
  7.                  v == (v & ~(v-1))]

  8. def print_event(filename, mask):
  9.     now = datetime.datetime.now()
  10.     print('{:02}:{:02}:{:02},{:03} file({file}) {mask}'.format(
  11.         now.hour, now.minute, now.second, now.microsecond//1000,
  12.         file=filename,
  13.         mask=' '.join(k for k,v in MASK_EVENTS if mask & v)))

  14. def main():
  15.     if len(sys.argv) != 2:
  16.         sys.exit('usage: {} path'.format(sys.argv[0]))
  17.     pathname = sys.argv[1]

  18.     notify = inotify.inotify()
  19.     wd = notify.add_watch(pathname, inotify.IN_ALL_EVENTS)
  20.     while True:
  21.         evs = notify.read_events(1024)
  22.         for wd,mask,cookie,name in evs:
  23.             print_event(pathname if name is None else name, mask)
  24.     notify.rm_watch(wd)
  25.     notify.close()

  26. if __name__ == '__main__':
  27.     main()
从编码风格来看,很难看出C style了,尤其是和C密切相关的结构体对齐和宏定义。
行2:inotify就是用C扩展的Python Module,所有C和OS的细节全部封装在内。
行5~8:此处仅为说明C扩展后的Module就像一个真正纯Python定义的Module,这个风格与Python的可读性原则不符,请勿模仿。if语句目的是提取模块中的mask常量定义,其一名称以“IN_”开头(如IN_OPEN等),其二名称不是IN_CLOEXEC和IN_NONBLOCK,它们是inotify_init的flags,不同于inotify_add_watch的flags,必须排除,其三为了打印输出简洁,必须是原子flag(如IN_MOVE_TO)而非复合flag(IN_MOVE == IN_MOVE_FROM | IN_MOVE_TO),,inotify定义的原子flag的数值均等于2 ** n(n 为非负整数),其数值满足位运算v == (v & ~(v-1))。
行25~26:经过封装返回的事件是Python的内置类型,此处是[(wd, mask, cookie, name), (...)],一个由tuple构成的list,文件名name是str。

2. C扩展分析。通过Python C API将inotify C API集成到Python类型系统中,参考了CPython源码树中的Modules/selectmodule.c,乍看比较复杂,其实多数是程式化步骤。如果没有接触过C/C++ Extending,建议先读下官方文档的扩展入门部分,然后结合下文分析会更容易理解。

写C扩展时,第一个要弄清楚的就是引用计数(reference count),下面列出有关tips:
  • reference count源于Python对象(对应C API的PyObject *)的内存管理,表示有多少个用户引用了该对象,当该值为0时,对象内存即被回收
  • Python对象的内存由Python堆内存管理器(Python heap manager)统一协调,意味着为Python对象分配内存,不能用C的malloc/free系列函数,因为他们是C heap,不同于Python heap,即使为非Python对象提供内存也应该用Python heap提供的PyMem_Malloc/PyMem_Free系列函数,便于Python heap做全局内存诊断和优化。
  • reference count的增大和减少,必须由用户显式控制,Py_INCREF(x)和Py_DECREF(x)分别是增加和减少x的引用计数,Py_INCREF(x)意味着用户对该对象的所有权,在显式Py_DECREF(x)之前,用户必定可以访问x而保证其不被回收。
  • reference count的变化意味着用户对该对象所有权(Ownership)的变化。作为函数入参的对象,Ownership通常不会变化,但有两个例外,其一是需要将该对象保存时,会调Py_INCREF,比如PyList_Append(),其二是遇到特例函数PyTuple_SetItem()/PyList_SetItem(),它们会接管所有权,而不调用Py_INCREF。作为函数返回值的对象,Ownership通常会改变,但有例外,这四个函数PyTuple_GetItem()/PyList_GetItem()/PyDict_GetItem()/PyDict_GetItemString()就是特例,它们返回的对象Ownership无变化,调用方只有使用权,如果想声明所有权必须显式调用Py_INCREF。

C扩展的前半部分,包括inotify的成员函数,也是模块使用者最应该关心的部分。

点击(此处)折叠或打开(inotifymodule.c)

  1. #include <Python.h>
  2. #include <sys/inotify.h>

  3. #define EVENT_SIZE_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
  4. #define EVENT_SIZE_MIN (sizeof(struct inotify_event))

  5. typedef struct {
  6.     PyObject_HEAD
  7.     int fd;
  8. } pyinotify;

  9. static PyObject *
  10. pyinotify_err_closed(void)
  11. {
  12.     PyErr_SetString(PyExc_ValueError,
  13.              "I/O operation on closed inotify object");
  14.     return NULL;
  15. }
  16. static int
  17. pyinotify_internal_close(pyinotify *self)
  18. {
  19.     int save_errno = 0;
  20.     if (self->fd >= 0) {
  21.         int fd = self->fd;
  22.         self->fd = -1;
  23.         Py_BEGIN_ALLOW_THREADS
  24.         if (close(fd) < 0)
  25.             save_errno = errno;
  26.         Py_END_ALLOW_THREADS
  27.     }
  28.     return save_errno;
  29. }

  30. static PyObject*
  31. pyinotify_get_closed(pyinotify *self)
  32. {
  33.     if (self->fd < 0)
  34.         Py_RETURN_TRUE;
  35.     else
  36.         Py_RETURN_FALSE;
  37. }

  38. PyDoc_STRVAR(pyinotify_add_watch_doc,
  39. "add_watch(pathname, mask) -> wd.\n\
  40. \n\
  41. Add a watch to the inotify instance, and watch descriptor returned.");

  42. static PyObject *
  43. pyinotify_add_watch(pyinotify *self, PyObject *args)
  44. {
  45.     PyObject *pathname;    
  46.     uint32_t mask;
  47.     int wd;

  48.     if (!PyArg_ParseTuple(args, "O&I:add_watch",
  49.                 PyUnicode_FSConverter, &pathname, &mask))
  50.         return NULL;

  51.     if (self->fd < 0)
  52.         return pyinotify_err_closed();

  53.     Py_BEGIN_ALLOW_THREADS
  54.     wd = inotify_add_watch(self->fd, PyBytes_AsString(pathname), mask);
  55.     Py_END_ALLOW_THREADS
  56.     Py_DECREF(pathname);    
  57.     if (wd == -1) {
  58.         PyErr_SetFromErrno(PyExc_OSError);
  59.         return NULL;
  60.     }

  61.     return PyLong_FromLong(wd);
  62. }

  63. PyDoc_STRVAR(pyinotify_rm_watch_doc,
  64. "rm_watch(wd) -> None.\n\
  65. \n\
  66. remove an existing watch descriptor from the inotify instance");

  67. static PyObject *
  68. pyinotify_rm_watch(pyinotify *self, PyObject *args)
  69. {
  70.     int wd;
  71.     int result;

  72.     if (!PyArg_ParseTuple(args, "i:rm_watch", &wd))
  73.         return NULL;

  74.     if (self->fd < 0)
  75.         return pyinotify_err_closed();

  76.     Py_BEGIN_ALLOW_THREADS
  77.     result = inotify_rm_watch(self->fd, wd);
  78.     Py_END_ALLOW_THREADS
  79.     if (result == -1) {
  80.         PyErr_SetFromErrno(PyExc_OSError);
  81.         return NULL;
  82.     }

  83.     Py_RETURN_NONE;
  84. }

  85. PyDoc_STRVAR(pyinotify_read_doc,
  86. "read(n) -> bytes\n\
  87. \n\
  88. Read events from the inotify instance.\n\
  89. at most n bytes will be returned.");

  90. static PyObject *
  91. pyinotify_read(pyinotify *self, PyObject *args)
  92. {
  93.     PyObject *evs;
  94.     Py_ssize_t result;
  95.     size_t nbyte;
  96.     char *buf;

  97.     if (!PyArg_ParseTuple(args, "I:read", &nbyte))
  98.         return NULL;
  99.     if (nbyte < EVENT_SIZE_MAX)
  100.         nbyte = EVENT_SIZE_MAX;

  101.     if (self->fd < 0)
  102.         return pyinotify_err_closed();

  103.     buf = (char *)PyMem_Malloc(nbyte);
  104.     if (buf == NULL) {
  105.         PyErr_NoMemory();
  106.         return NULL;
  107.     }
  108.     Py_BEGIN_ALLOW_THREADS
  109.     result = read(self->fd, buf, nbyte);
  110.     Py_END_ALLOW_THREADS
  111.     if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
  112.         PyMem_Free(buf);
  113.         PyErr_SetFromErrno(PyExc_OSError);
  114.         return NULL;
  115.     }
  116.     evs = PyBytes_FromStringAndSize(buf, result);
  117.     PyMem_Free(buf);

  118.     return evs;
  119. }

  120. PyDoc_STRVAR(pyinotify_read_events_doc,
  121. "read_events(n) -> [(wd, mask, cookie, name), (...)].\n\
  122. \n\
  123. Read events from the inotify instance.\n\
  124. at most n bytes will be read, and then unpacked.");

  125. static PyObject *
  126. pyinotify_read_events(pyinotify *self, PyObject *args)
  127. {
  128.     PyObject *elist = NULL;
  129.     PyObject *etuple = NULL;
  130.     struct inotify_event *eptr = NULL;
  131.     size_t nbyte = 0;
  132.     char *buf = NULL;
  133.     Py_ssize_t result = -1; /* bytes in #buf. */
  134.     Py_ssize_t cnt = 0; /* count the events in #buf. */
  135.     Py_ssize_t offset = 0; /* offset in #buf or #elist. */

  136.     if (!PyArg_ParseTuple(args, "I:read", &nbyte))
  137.         return NULL;
  138.     if (nbyte < EVENT_SIZE_MAX)
  139.         nbyte = EVENT_SIZE_MAX;

  140.     if (self->fd < 0)
  141.         return pyinotify_err_closed();

  142.     buf = (char *)PyMem_Malloc(nbyte);
  143.     if (buf == NULL) {
  144.         PyErr_NoMemory();
  145.         return NULL;
  146.     }
  147.     Py_BEGIN_ALLOW_THREADS
  148.     result = read(self->fd, buf, nbyte);
  149.     Py_END_ALLOW_THREADS
  150.     if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
  151.         PyErr_SetFromErrno(PyExc_OSError);
  152.         goto error;
  153.     }

  154.     for (cnt = 0, offset = 0; offset < result; ++cnt) {
  155.         eptr = (struct inotify_event *)(buf + offset);
  156.         offset += sizeof(*eptr) + eptr->len;
  157.     }
  158.     elist = PyList_New(cnt);
  159.     if (elist == NULL) {
  160.         PyErr_NoMemory();
  161.         goto error;
  162.     }

  163.     eptr = (struct inotify_event *)buf;
  164.     for (offset = 0; offset < cnt; ++offset) {
  165.         if (eptr->len > 1) {
  166.             etuple = Py_BuildValue("iIIO&",
  167.                      eptr->wd, eptr->mask, eptr->cookie,
  168.                          PyUnicode_DecodeFSDefault, eptr->name);
  169.         } else {
  170.             etuple = Py_BuildValue("iIIy",
  171.                      eptr->wd, eptr->mask, eptr->cookie, NULL);
  172.         }
  173.         if (etuple == NULL) {
  174.             Py_CLEAR(elist);
  175.             goto error;
  176.         }
  177.         if (PyList_SetItem(elist, offset, etuple) == -1) {
  178.             Py_CLEAR(etuple);
  179.             Py_CLEAR(elist);
  180.             goto error;
  181.         }
  182.         eptr = (struct inotify_event *)((char *)eptr + sizeof(*eptr) + eptr->len);
  183.     }

  184. error:
  185.     PyMem_Free(buf);
  186.     return elist;
  187. }

  188. PyDoc_STRVAR(pyinotify_close_doc,
  189. "close() -> None.\n\
  190. \n\
  191. close the inotify instance");

  192. static PyObject *
  193. pyinotify_close(pyinotify *self, PyObject *args)
  194. {
  195.     errno = pyinotify_internal_close(self);
  196.     if (errno < 0) {
  197.         PyErr_SetFromErrno(PyExc_OSError);
  198.         return NULL;
  199.     }
  200.     Py_RETURN_NONE;
  201. }
仔细浏览会发现各函数实现大同小异,这里只分析一个典型的函数pyinotify_read_events
行7~10:inotify对象的实际定义,唯一可见的成员就是inotify_init1返回的fd。

行143~147:pyinotify_read_events的文档,是函数实现的一部分,按照标准库的风格,硬编了几句英文。
行149~150:pyinotify_read_events的接口声明,该函数与Python代码直接交互,返回值必须是PyObject *,作为inotify对象的成员函数,第一个参数是对象本身PyObject *self,第二个参数是函数需要的所有参数PyObject *args
行161~162:解析pyinotify_read_events被调用时的入参,这是一个标准步骤,其他函数也是类似的。如果解析失败,PyArg_ParseTuple内部会设置异常类型,“return NULL”将告诉解释器终止当前代码运行,抛出异常。
行169~173:分配内存以存储内核将来返回的事件,最好利用Python heap提供的函数PyErr_NoMemory,当分配失败后,用户负责设置异常类型PyErr_NoMemory。
行174~176:读取内核事件,进行IO操作的前后,必须分别释放Py_BEGIN_ALLOW_THREADS和获取Py_END_ALLOW_THREADS全局解释锁(GIL,global interpreter lock),该原则适用所有C扩展。
行182~185:获取buf中事件的总数,struct inotify_event的真实内容是变长的,这里只能遍历一次buf才能获取事件总数。
行195~197:每个struct inotfiy_event构造一个tuple,read返回文件名是一个C字符串,要用PyUnicode_DecodeFSDefault解码为Python的str。
行206~209:每个struct inotfiy_event产生的etuple(对应Python tuple)都会保存到elist(对应Python list),注意此处PyList_SetItem会直接接管etuple对象,而不进行Py_INCREF(etuple),这是Python C API中为数不多的特例。如果执行失败,保证etuple, elist, buf的内存都要释放。


(更多内容参见下篇)
阅读(1284) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~