Python C API: 创建一定数量的 PyObject 后出现 WindowsError

Python C API: WindowsError after creating some number of PyObjects

我一直在让 Python C API 不给我错误。

背景: 我一直在使用 ctypes 到 运行 本机代码 (C++) 一段时间,但直到现在我还没有真正做过Python C API 的任何特定内容。我主要只是从 Python 传递结构并从 C++ 填充它们。我使用结构的方式变得很麻烦,所以我决定尝试直接在 C++ 中创建 Python 对象,然后将它们传回我的 Python 脚本。

代码: 我有一个只有一个功能的 DLL (Foo.dll):

#define N 223
__declspec(dllexport) void Bar(void)
{
    std::cout << "Bar" << std::endl;

    for (int i = 0; i < N; ++i)
    {
        auto list = PyList_New(0);
        std::cout << "Created: " << i << std::endl;
        //Py_DECREF(list);
    }
}

然后我有 Python 脚本我 运行ning:

import ctypes as C
dll = r"C:\path\to\dll\Foo.dll"
Foo = C.CDLL(dll)
# lists = [[] for _ in range(0)]
Foo.Bar()
print "Done."

会发生什么: 如果我在上面的 DLL 中将 N 定义为 222 或以下,代码工作正常(除了内存泄漏,但这不是问题)。

如果我取消注释 //Py_DECREF(list) 行,代码就可以正常工作。

但是,通过上面的代码,我得到了这个:

Bar
Created: 0
Created: 1
Created: 2
...(omitted for your sake)
Created: 219
Created: 220
Created: 221
Traceback (most recent call last):
  File "C:\path_to_script\script.py", line 9, in <module>
    Foo.Bar()
WindowsError: exception: access violation reading 0x00000028

事实上,我用字典、列表、元组等得到了同样的结果。如果我创建一个列表然后将空子列表附加到该列表,我会得到相同的结果。

更奇怪的是,我在实际 Python 脚本中制作的每个列表都会减少 DLL 在出现此 windows 错误之前可以制作的列表数量。

更奇怪的是,如果我在我的 python 脚本中创建超过 222 个列表,那么 DLL 在创建类似 720 more 之前不会遇到此错误列出。

**其他细节:**

只要我不从我的 C++ 代码中创建很多 PyObject,一切似乎都运行良好。我可以在 Python 代码之间传递 PyObjects 并且它工作正常..直到我从我的 C++ 代码中创建了 "too many" 个对象。

这是怎么回事?

From the documentation for CDLL:

The Python global interpreter lock is released before calling any function exported by these libraries, and reacquired afterwards.

这使得 不安全 使用 Python C API 代码。正如您所发现的,它究竟是如何失败的是不可预测的。我猜这与分配是否触发垃圾收集器的 运行 有关,但我认为不值得花太多时间来找出确切原因。

有(至少)两种解决方案可供选择:

  1. 使用ctypes.PyDLL(文档注释类似于CDLL,只是它不释放GIL)
  2. 在您的 C++ 代码中重新获取 GIL - 一个简单的方法是:

    auto state = PyGILState_Ensure();
    // C++ code requiring GIL - probably your entire loop
    PyGILState_Release(state);