使用Python C API时检查返回值有多重要?
似乎每次调用返回PyObject*的函数时,我都必须添加四行错误检查。例如:使用Python C API时检查返回值有多重要?,python,c,memory-management,Python,C,Memory Management,似乎每次调用返回PyObject*的函数时,我都必须添加四行错误检查。例如: py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename); if (!py_fullname) { Py_DECREF(pygame); Py_DECREF(os); return NULL; } image = PyObject_CallMethodObjArgs(pygame, "image.load
py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename);
if (!py_fullname) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
image = PyObject_CallMethodObjArgs(pygame, "image.load", py_fullname, NULL);
Py_DECREF(py_fullname);
if (!image) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
image = PyObject_CallMethodObjArgs(image, "convert", NULL);
if (!image) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
我错过什么了吗?有更好的方法吗?这增加了一个问题,那就是我可能会忘记所有我应该做的
Py_DECREF()
这是C API。如果只在C中编码,则可能需要使用它,但是如果应用程序是用C++编码的,那么您可能需要查看A.< /P>< P>这就是为什么代码> Goto < /C> >在C代码中是活的(尽管不是完全好的)——(与C++和支持异常的其他语言相反):这是唯一一种不在代码主线上重复终止块的好方法——在每次返回值检查时,有条件地向前跳转到errorexit
,带有标签errorexit:
,该标签执行递减操作(文件关闭,以及终止时需要执行的任何操作)而result = NULL;
helper = allocate_something;
if (!helper) goto return_result;
result = allocate_something_else;
if (!result) goto error_return; // OK, result is already NULL, but it makes the point
result->contents = allocate_another_thing;
if (!result->contents) goto error_cleanup_result;
result->othercontents = allocate_last_thing;
if (!result->othercontents) goto error_cleanup_contents;
free_helper:
free(helper);
return_result:
return result;
error_cleanup_contents:
free(result->contents);
error_cleanup_result:
free(result);
error_return;
result = NULL;
goto free_helper;
CLEANUP_VOID(Py_DECREF, py_fullname)
CLEANUP_VOID(Py_DECREF, pygame)
CLEANUP_VOID(Py_DECREF, os)
是的,这很可怕,Python或C++程序员在看到它时身体会很不舒服。如果我再也不用写这样的代码了,我就不会那么失望了。但是,只要您有一个关于如何清理资源的系统方案,您就应该始终准确地知道在出现错误时跳转到哪个错误标签,并且该错误标签应该“知道”以清理迄今为止分配的所有资源。按相反的顺序操作可以让fall-through共享代码。一旦你习惯了,做两件事就相当容易了:首先,沿着从任何给定的错误标签到出口的路径,确认所有应该释放的都被释放了。其次,查看两种错误情况之间的差异,并确认这是所需错误处理之间的正确差异,因为这种差异正是为了将在跳转之间分配的内容释放到这些标签
这就是说,一个半体面的优化编译器将为您的示例中的错误情况共享代码。当你有代码拷贝并粘贴在这样的地方时,更容易出错,尤其是当你稍后修改它的时候。 虽然我不经常看到它,但我认为这是C程序员(“Toto TooType”)的一个很好的解决方案: 有几个优点:
#define CATCH_BEGIN do {
#define CATCH_END } while (1!=1);
#define CLEANUP_VOID(function,var) {if (var != NULL) { function(var); var = NULL;}}
这样可以在末尾进行如下清理:
result = NULL;
helper = allocate_something;
if (!helper) goto return_result;
result = allocate_something_else;
if (!result) goto error_return; // OK, result is already NULL, but it makes the point
result->contents = allocate_another_thing;
if (!result->contents) goto error_cleanup_result;
result->othercontents = allocate_last_thing;
if (!result->othercontents) goto error_cleanup_contents;
free_helper:
free(helper);
return_result:
return result;
error_cleanup_contents:
free(result->contents);
error_cleanup_result:
free(result);
error_return;
result = NULL;
goto free_helper;
CLEANUP_VOID(Py_DECREF, py_fullname)
CLEANUP_VOID(Py_DECREF, pygame)
CLEANUP_VOID(Py_DECREF, os)
“化妆品”如
#定义EXCIFZERO(x)如果(!x)转到errorexit
可以帮助您为什么不使用Cython+C?我遇到的唯一一个必须使用Python C API的情况是,在Cython调用的一些线程C代码中,有时不得不调用纯Python代码(它代表了大约10行Python C API)。。。在所有其他情况下,我都能够用Cython完全包装纯C代码,而且从不担心C API。这很有趣,我不知道。尽管如此,这更像是一个借口来练习一点C,让它做一些有用的事情。有趣的是,这根本没有被投票支持。这是一篇很好的文章,介绍了在C语言中,如何在返回前进行清洗。
CLEANUP_VOID(Py_DECREF, py_fullname)
CLEANUP_VOID(Py_DECREF, pygame)
CLEANUP_VOID(Py_DECREF, os)