Python 受限 API:如何设置扩展类型的 __module__ 属性

问题描述

使用“受限 API”时,PyTypeObject 的结构不可用。所以,你不能定义静态分配的类型对象。

对于动态分配的类型对象(又名堆类型),您可以通过 PyType_Spec.name 指定类型的名称。 文档说:“对于动态分配的类型对象,这应该只是类型名称,模块名称显式存储在类型字典中作为键 '__module__' 的值。

但是没有用于设置 PyTypeObject.tp_dict 的类型槽。那么,如何设置模块名称

您可以在创建类型后动态执行此操作。但这给出了“DeprecationWarning: builtin type ... has no __module__ attribute”。

解决方法

__module__ 是根据类型的 tp_name 计算的,它应该看起来像 "module_name.TypeName"。在 C 代码中创建堆类型时,通过 tp_name 指定 PyType_Spec.name

tp_name docs 说了一些矛盾的话:

对于动态分配的类型对象,这应该只是类型名称,以及明确存储在类型字典中作为键 '__module__' 的值的模块名称。

但是对于像 PyType_FromModuleAndSpec 这样在 C 代码中动态分配类型对象的调用来说,这些文档似乎是错误的。检查 implementation 表明它希望模块名称成为 tp_name 的一部分:

    /* Set type.__module__ */
    if (_PyDict_GetItemIdWithError(type->tp_dict,&PyId___module__) == NULL) {
        if (PyErr_Occurred()) {
            goto fail;
        }
        s = strrchr(spec->name,'.');
        if (s != NULL) {
            int err;
            modname = PyUnicode_FromStringAndSize(
                    spec->name,(Py_ssize_t)(s - spec->name));
            if (modname == NULL) {
                goto fail;
            }
            err = _PyDict_SetItemId(type->tp_dict,&PyId___module__,modname);
            Py_DECREF(modname);
            if (err != 0)
                goto fail;
        } else {
            if (PyErr_WarnFormat(PyExc_DeprecationWarning,1,"builtin type %.200s has no __module__ attribute",spec->name))
                goto fail;
        }
    }

并检查标准库中的 PyType_Specuses 表明模块名称在 tp_name 中:

static PyType_Spec Comptype_spec = {
    "zlib.Compress",sizeof(compobject),Py_TPFLAGS_DEFAULT,Comptype_slots
};

请注意,尽管 PyType_FromModuleAndSpec 将模块作为参数,但该模块并未用于设置 __module__