使用 Python C-API 定义内部 class

Defining an inner class using the Python C-API

在Python中直接定义一个内层class:

class MyClass(object):

    class MyInnerClass(object):
        pass

… 可以像预期的那样访问内部 class,例如通过 MyClass.MyInnerClass.

我正在尝试使用扩展模块设置类似的东西。通常,一个人将自己定义的扩展类型添加到模块的 <modulename>init() 函数中的扩展模块对象,代码如下:

/// …
if (PyType_Ready(&BufferModel_Type) < 0)      { return; }

/// Add the BufferModel type object to the module
Py_INCREF(&BufferModel_Type);
PyModule_AddObject(module,
    "Buffer",
    (PyObject*)&BufferModel_Type);

/// …

为了设置内部 class,我改变了这种方法来尝试添加一个 PyTypeObject* 作为另一个 PyTypeObject* 的属性,如下所示:

/// …
if (PyType_Ready(&ImageBufferModel_Type) < 0) { return; }
if (PyType_Ready(&ImageModel_Type) < 0)       { return; }

/// Add the ImageBufferModel type object to im.Image
Py_INCREF(&ImageBufferModel_Type);
PyObject_SetAttrString((PyObject*)&ImageModel_Type,
    "ImageBuffer",
    (PyObject*)&ImageBufferModel_Type);
PyType_Modified((PyTypeObject*)&ImageModel_Type);

/// Add the ImageModel type object to the module
Py_INCREF(&ImageModel_Type);
PyModule_AddObject(module,
    "Image",
    (PyObject*)&ImageModel_Type);

/// …

…我认为 PyObject_SetAttrString() 会起作用,因为 introduction to “Type Objects” in the C-API docs 具体说:

Type objects can be handled using any of the PyObject_*() or PyType_*() functions […]

… 我在 its description in the docs 的基础上添加了调用 PyType_Modified()。但是这样:当我编译所有内容并尝试加载扩展时,出现此错误:

>>> import im
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    import im
  File "im/__init__.py", line 2, in <module>
    from im import (
TypeError: can't set attributes of built-in/extension type 'im.Image'

…我想我的做法是错误的;我应该尝试什么?

为此你需要直接使用tp_dict:

This field should normally be initialized to NULL before PyType_Ready is called; it may also be initialized to a dictionary containing initial attributes for the type. Once PyType_Ready() has initialized the type, extra attributes for the type may be added to this dictionary only if they don’t correspond to overloaded operations (like __add__()).

除了使用 PyObject_SetAttrString() 你还可以

PyDict_SetItemString(ImageModel_Type.tp_dict, "ImageBuffer", (PyObject*) &ImageModel_Type);

但在这种情况下,文档中的警告适用:

It is not safe to use PyDict_SetItem() on or otherwise modify tp_dict with the dictionary C-API.

所以也许在 ImageModel_Type 上调用 PyType_Ready 之前初始化 tp_dict:

/// Initialize tp_dict with empty dictionary
ImageModel_Type.tp_dict = PyDict_New();
if (!ImageModel_Type.tp_dict) { return; }

/// Add the ImageBufferModel type object to im.Image
if (PyType_Ready(&ImageBufferModel_Type) < 0) { return; }
Py_INCREF(&ImageBufferModel_Type);
PyDict_SetItemString(ImageModel_Type.tp_dict,
    "ImageBuffer",
    (PyObject*)&ImageBufferModel_Type);

/// Add the ImageModel type object to the module
if (PyType_Ready(&ImageModel_Type) < 0) { return; }
Py_INCREF(&ImageModel_Type);
PyModule_AddObject(module,
    "Image",
    (PyObject*)&ImageModel_Type);