python time库引用不正确的是_浅析Python调用外部代码的三种方式(中篇)

(承接上篇)

六. 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的内存都要释放。

(更多内容参见下篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值