python - 使用 Python C-API 的类属性

标签 python python-c-api

使用 Python C-API 创建类属性(如 herehere)的最佳方法是什么?静态属性也适用于我的情况。

跟进:

我尝试执行 yak 的建议。我定义了一个 P 类,在其 tp_descr_gettp_descr_set 槽中带有 get 和 set 函数。然后,我将 P 的一个实例添加到键 p 下的类 X 的类型对象的字典中。然后

x1 = X()
x2 = X()
x1.p = 10
print x1.p
print x2.p
print X.p
x2.p = 11
print x1.p
print x2.p
print X.p

有效(前 10 打印 3 次,然后 11 打印 3 次),但是

X.p = 12

失败并显示错误消息

TypeError: can't set attributes of built-in/extension type 'X'

我该如何解决?

跟进 2:

如果我使用 PyMem_Malloc 分配类型对象并设置 Py_TPFLAGS_HEAPTYPE 标志,那么一切正常;我可以用预期的结果执行 X.p = 12

如果我将类型对象保存在静态变量中并设置 Py_TPFLAGS_HEAPTYPE 标志,事情也会起作用,但这显然不是一个好主意。 (但是为什么类型对象是在静态内存还是动态内存中很重要?无论如何我都不会让它的引用计数下降到 0。)

只能在动态类型上设置属性的限制看起来很奇怪。这背后的原理是什么?

跟进 3:

不,它不起作用。如果我使类型 X 动态,则 X.p = 12 不会将属性 X.p 设置为十二;它实际上将对象 12 绑定(bind)到名称 X.p。换句话说,之后,X.p 不是整数值属性,而是整数。

跟进 4:

这是扩展的 C++ 代码:

#include <python.h>
#include <exception>

class ErrorAlreadySet : public std::exception {};

// P type ------------------------------------------------------------------

struct P : PyObject
{
    PyObject* value;
};

PyObject* P_get(P* self, PyObject* /*obj*/, PyObject* /*type*/)
{
    Py_XINCREF(self->value);
    return self->value;
}

int P_set(P* self, PyObject* /*obj*/, PyObject* value)
{
    Py_XDECREF(self->value);
    self->value = value;
    Py_XINCREF(self->value);
    return 0;
}

struct P_Type : PyTypeObject
{
    P_Type()
    {
        memset(this, 0, sizeof(*this));
        ob_refcnt = 1;
        tp_name = "P";
        tp_basicsize = sizeof(P);
        tp_descr_get = (descrgetfunc)P_get;
        tp_descr_set = (descrsetfunc)P_set;
        tp_flags = Py_TPFLAGS_DEFAULT;

        if(PyType_Ready(this)) throw ErrorAlreadySet();
    }
};

PyTypeObject* P_type()
{
    static P_Type typeObj;
    return &typeObj;
}


// P singleton instance ----------------------------------------------------

P* createP()
{
    P* p_ = PyObject_New(P, P_type());
    p_->value = Py_None;
    Py_INCREF(p_->value);
    return p_;
}

P* p()
{
    static P* p_ = createP();
    Py_INCREF(p_);
    return p_;
}

PyObject* p_value()
{
    PyObject* p_ = p();
    PyObject* value = p()->value;
    Py_DECREF(p_);
    Py_INCREF(value);
    return value;
}


// X type ------------------------------------------------------------------

struct X : PyObject {};

void X_dealloc(PyObject* self)
{
    self->ob_type->tp_free(self);
}

struct X_Type : PyTypeObject
{
    X_Type()
    {
        memset(this, 0, sizeof(*this));
        ob_refcnt = 1;
        tp_name = "M.X";
        tp_basicsize = sizeof(X);
        tp_dealloc = (destructor)X_dealloc;
        tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE;

        tp_dict = PyDict_New();
        PyObject* key = PyString_FromString("p");
        PyObject* value = p();
        PyDict_SetItem(tp_dict, key, value);
        Py_DECREF(key);
        Py_DECREF(value);

        if(PyType_Ready(this)) throw ErrorAlreadySet();
    }

    void* operator new(size_t n) { return PyMem_Malloc(n); }
    void operator delete(void* p) { PyMem_Free(p); }
};

PyTypeObject* X_type()
{
    static PyTypeObject* typeObj = new X_Type;
    return typeObj;
}

// module M ----------------------------------------------------------------

PyMethodDef methods[] = 
{
    {"p_value", (PyCFunction)p_value, METH_NOARGS, 0},
    {0, 0, 0, 0}
};

PyMODINIT_FUNC
initM(void)
{
    try {
        PyObject* m = Py_InitModule3("M", methods, 0);
        if(!m) return;
        PyModule_AddObject(m, "X", (PyObject*)X_type());
    }
    catch(const ErrorAlreadySet&) {}
}

这段代码定义了一个模块M,它有一个类X,类属性p,如前所述。我还添加了一个函数 p_value(),让您可以直接检查实现该属性的对象。

这是我用来测试扩展的脚本:

from M import X, p_value

x1 = X()
x2 = X()

x1.p = 1
print x1.p
print x2.p
print X.p
print p_value()
print

x2.p = 2
print x1.p
print x2.p
print X.p
print p_value()
print

X.p = 3
print x1.p
print x2.p
print X.p
print p_value()     # prints 2
print

x1.p = 4       # AttributeError: 'M.X' object attribute 'p' is read-only

最佳答案

与这些 Python 解决方案类似,您必须在 C 中创建一个 classproperty 类型并实现其 tp_descr_get 函数(对应于 __get__在 Python 中)。

然后,如果你想在 C 类型中使用它,你必须创建一个 classproperty 类型的实例并将其插入到你的类型的字典中(tp_dict 您类型的广告位)。

跟进:

似乎不可能设置 C 类型的属性。元类 (PyType_Type) 的 tp_setattro 函数为所有非堆类型(没有Py_TPFLAGS_HEAPTYPE 标志)。此标志是为动态类型设置的。您可以使您的类型动态化,但它可能比它值得做更多的工作。

这意味着我最初给出的解决方案允许您在 C 类型对象上创建属性(如:计算属性),但限制为只读。对于设置,您可以在 tp_methods 中的方法上使用类/静态方法(METH_CLASS/METH_STATIC 标志)。

关于python - 使用 Python C-API 的类属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10161609/

相关文章:

Python 调用 C : how could C send Python's line number it has been called from?

python - 根据转换是否可以增量运行来更改输入

python - 通过单击两个键以 OpenCV VideoCapture 结束时发出关闭视频的问题

python - 链式 Python 类调用

python - 如何使用python查找xml文件中的元素并替换同名元素的值?

python - 找不到满足我自己模块要求的版本

Python 3.3 C-API 和 UTF-8 字符串

python - 我在哪里可以找到 Python 3 标准库中的实例方法?

c++ - 是否可以在运行时修改 PYTHONPATH?

python - 在 C 和 Python 之间传递 C++ 指针