经过前面的一系列铺垫,现在要迎来我们的终极成果了——在运行我们自定义的函数过程中,如果要停止、暂停和再恢复python解释器,应该如何操作呢?
如果自定义函数中有耗时操作应该如何处理呢?
如何通过python c api实现钩子的功能呢?
先上码:
int PythonRunner::tracer(PyObject *, _frame *, int, PyObject *)
{
//Pause is requested...
if (_instance->pauseRequested())
{
Py_BEGIN_ALLOW_THREADS;
//If we are paused and no resume has been requested sleep
while (!_instance->resumeRequested())
{
QThread::msleep(500);
}
Py_END_ALLOW_THREADS;
}
//Abort requested
else if (true == _instance->interruptRequested())
{
Py_AddPendingCall(&PythonRunner::raiseException, NULL);
}
// Otherwise proceed normally.
return 0;
}
int PythonRunner::raiseException(void *)
{
// PyErr_SetInterrupt();
PyErr_SetString(PyExc_KeyboardInterrupt, "Abort");
return -1;
}
void PythonRunner::run()
{
PyEval_SetTrace(PythonRunner::tracer, NULL);
runPython();
}
static int tracer(PyObject *, struct _frame *, int, PyObject *);
static int raiseException(void *);
由于整个工程的代码贴上去会比较乱,只把主要的部分说下。
static int tracer(PyObject *, struct _frame *, int, PyObject *)该函数被用于注册回调, 用它可以实现钩子的功能。啥是钩子?简单来说就是执行每行代码前都会进入这个回调函数。注意struct _frame该结构体,需要包含#include <python3.5/frameobject.h>这个头文件,否则会报错。暂停、恢复和停止的功能也是在该函数中实现的。
我们知道在终端上运行python时,可以通过ctrl + c 来终止运行的python脚本,并且会弹出很多信息。我们的停止功能和这个相同,不过还有另一种接口调用。停止功能的函数需要返回-1,,并且调用PyErr_SetInterrupt()或PyErr_SetString(PyExc_KeyboardInterrupt, "Abort")。调用第一个是直接停止python解释器,不带有返回信息;调用第二个相当于使用ctrl + c来终止程序,带有返回信息,对用户输出内容这里包含About。异常停止的函数由c api Py_AddPendingCall()来调用。这里需要注意的是Py_AddPendingCall()该函数需要和python执行PyRun_SimpleString()的调用在同一个线程里面。
暂停的功能则是在调用每条指令前进行拦截。
为了防止自定义python中执行while 耗时操作,故将PyRun_SimpleString()放在线程中执行,这样就不会阻塞UI界面了。而我们也将回调函数注册到了线程里面。
这里面要注意的是当停止按钮按下后,被中断的线程需要根据对应的业务逻辑做对应的处理,有关线程的处理是很有考究的。