赞
踩
(承接上篇)
六. C/C++ Extending标准库
场景。无可用Python模块,将C API整合到Python类型系统中,造福众人。
实现。
1. 假设已经写好C扩展(后面介绍实现),Python调用C扩展再次实现一遍同样的需求。
点击(此处)折叠或打开(notify3.py)
import datetime
import inotify
import sys
MASK_EVENTS = [(k,v) for k,v in sorted(list(inotify.__dict__.items()))
if k.startswith('IN_') and \
k not in ('IN_CLOEXEC', 'IN_NONBLOCK') and \
v == (v & ~(v-1))]
def print_event(filename, mask):
now = datetime.datetime.now()
print('{:02}:{:02}:{:02},{:03} file({file}) {mask}'.format(
now.hour, now.minute, now.second, now.microsecond//1000,
file=filename,
mask=' '.join(k for k,v in MASK_EVENTS if mask & v)))
def main():
if len(sys.argv) != 2:
sys.exit('usage: {} path'.format(sys.argv[0]))
pathname = sys.argv[1]
notify = inotify.inotify()
wd = notify.add_watch(pathname, inotify.IN_ALL_EVENTS)
while True:
evs = notify.read_events(1024)
for wd,mask,cookie,name in evs:
print_event(pathname if name is None else name, mask)
notify.rm_watch(wd)
notify.close()
if __name__ == '__main__':
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,建议先读下官方文档的扩展入门部分http://docs.python.org/3/extending/extending.html,然后结合下文分析会更容易理解。
写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)
#include
#include
#define EVENT_SIZE_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
#define EVENT_SIZE_MIN (sizeof(struct inotify_event))
typedef struct {
PyObject_HEAD
int fd;
} pyinotify;
static PyObject *
pyinotify_err_closed(void)
{
PyErr_SetString(PyExc_ValueError,
"I/O operation on closed inotify object");
return NULL;
}
static int
pyinotify_internal_close(pyinotify *self)
{
int save_errno = 0;
if (self->fd >= 0) {
int fd = self->fd;
self->fd = -1;
Py_BEGIN_ALLOW_THREADS
if (close(fd) < 0)
save_errno = errno;
Py_END_ALLOW_THREADS
}
return save_errno;
}
static PyObject*
pyinotify_get_closed(pyinotify *self)
{
if (self->fd < 0)
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
PyDoc_STRVAR(pyinotify_add_watch_doc,
"add_watch(pathname, mask) -> wd.\n\
\n\
Add a watch to the inotify instance, and watch descriptor returned.");
static PyObject *
pyinotify_add_watch(pyinotify *self, PyObject *args)
{
PyObject *pathname;
uint32_t mask;
int wd;
if (!PyArg_ParseTuple(args, "O&I:add_watch",
PyUnicode_FSConverter, &pathname, &mask))
return NULL;
if (self->fd < 0)
return pyinotify_err_closed();
Py_BEGIN_ALLOW_THREADS
wd = inotify_add_watch(self->fd, PyBytes_AsString(pathname), mask);
Py_END_ALLOW_THREADS
Py_DECREF(pathname);
if (wd == -1) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyLong_FromLong(wd);
}
PyDoc_STRVAR(pyinotify_rm_watch_doc,
"rm_watch(wd) -> None.\n\
\n\
remove an existing watch descriptor from the inotify instance");
static PyObject *
pyinotify_rm_watch(pyinotify *self, PyObject *args)
{
int wd;
int result;
if (!PyArg_ParseTuple(args, "i:rm_watch", &wd))
return NULL;
if (self->fd < 0)
return pyinotify_err_closed();
Py_BEGIN_ALLOW_THREADS
result = inotify_rm_watch(self->fd, wd);
Py_END_ALLOW_THREADS
if (result == -1) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(pyinotify_read_doc,
"read(n) -> bytes\n\
\n\
Read events from the inotify instance.\n\
at most n bytes will be returned.");
static PyObject *
pyinotify_read(pyinotify *self, PyObject *args)
{
PyObject *evs;
Py_ssize_t result;
size_t nbyte;
char *buf;
if (!PyArg_ParseTuple(args, "I:read", &nbyte))
return NULL;
if (nbyte < EVENT_SIZE_MAX)
nbyte = EVENT_SIZE_MAX;
if (self->fd < 0)
return pyinotify_err_closed();
buf = (char *)PyMem_Malloc(nbyte);
if (buf == NULL) {
PyErr_NoMemory();
return NULL;
}
Py_BEGIN_ALLOW_THREADS
result = read(self->fd, buf, nbyte);
Py_END_ALLOW_THREADS
if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
PyMem_Free(buf);
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
evs = PyBytes_FromStringAndSize(buf, result);
PyMem_Free(buf);
return evs;
}
PyDoc_STRVAR(pyinotify_read_events_doc,
"read_events(n) -> [(wd, mask, cookie, name), (...)].\n\
\n\
Read events from the inotify instance.\n\
at most n bytes will be read, and then unpacked.");
static PyObject *
pyinotify_read_events(pyinotify *self, PyObject *args)
{
PyObject *elist = NULL;
PyObject *etuple = NULL;
struct inotify_event *eptr = NULL;
size_t nbyte = 0;
char *buf = NULL;
Py_ssize_t result = -1; /* bytes in #buf. */
Py_ssize_t cnt = 0; /* count the events in #buf. */
Py_ssize_t offset = 0; /* offset in #buf or #elist. */
if (!PyArg_ParseTuple(args, "I:read", &nbyte))
return NULL;
if (nbyte < EVENT_SIZE_MAX)
nbyte = EVENT_SIZE_MAX;
if (self->fd < 0)
return pyinotify_err_closed();
buf = (char *)PyMem_Malloc(nbyte);
if (buf == NULL) {
PyErr_NoMemory();
return NULL;
}
Py_BEGIN_ALLOW_THREADS
result = read(self->fd, buf, nbyte);
Py_END_ALLOW_THREADS
if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
for (cnt = 0, offset = 0; offset < result; ++cnt) {
eptr = (struct inotify_event *)(buf + offset);
offset += sizeof(*eptr) + eptr->len;
}
elist = PyList_New(cnt);
if (elist == NULL) {
PyErr_NoMemory();
goto error;
}
eptr = (struct inotify_event *)buf;
for (offset = 0; offset < cnt; ++offset) {
if (eptr->len > 1) {
etuple = Py_BuildValue("iIIO&",
eptr->wd, eptr->mask, eptr->cookie,
PyUnicode_DecodeFSDefault, eptr->name);
} else {
etuple = Py_BuildValue("iIIy",
eptr->wd, eptr->mask, eptr->cookie, NULL);
}
if (etuple == NULL) {
Py_CLEAR(elist);
goto error;
}
if (PyList_SetItem(elist, offset, etuple) == -1) {
Py_CLEAR(etuple);
Py_CLEAR(elist);
goto error;
}
eptr = (struct inotify_event *)((char *)eptr + sizeof(*eptr) + eptr->len);
}
error:
PyMem_Free(buf);
return elist;
}
PyDoc_STRVAR(pyinotify_close_doc,
"close() -> None.\n\
\n\
close the inotify instance");
static PyObject *
pyinotify_close(pyinotify *self, PyObject *args)
{
errno = pyinotify_internal_close(self);
if (errno < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}
仔细浏览会发现各函数实现大同小异,这里只分析一个典型的函数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的内存都要释放。
(更多内容参见下篇)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。