使用Python C-API的类属性

Joh*_*åde 7 python python-c-api

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

跟进:

我试图实施牦牛的建议.我P在其槽tp_descr_gettp_descr_set槽中定义了一个带有get和set函数的类.然后我在键下面的P类的类型对象的字典中添加了一个实例.然后Xp

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
Run Code Online (Sandbox Code Playgroud)

作品(前10张印刷三次,然后11张印刷三次),但是

X.p = 12
Run Code Online (Sandbox Code Playgroud)

失败并显示错误消息

TypeError: can't set attributes of built-in/extension type 'X'
Run Code Online (Sandbox Code Playgroud)

我该如何解决这个问题?

跟进2:

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

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

您只能在动态类型上设置属性的限制似乎很奇怪.这背后的理由是什么?

跟进3:

不,它不起作用.如果我将类型设置为X动态,X.p = 12则不将属性设置X.p为12; 它实际上将对象绑定12到名称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&) {}
}
Run Code Online (Sandbox Code Playgroud)

此代码定义了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
Run Code Online (Sandbox Code Playgroud)

yak*_*yak 5

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

然后,如果你想使用在C型,你就必须创建的实例classproperty类型并将其插入到你的类型(的字典tp_dict你的类型的插槽).

跟进:

似乎不可能设置C类型的属性.对于所有非堆类型(没有标志的类型)tp_setattro,metaclass(PyType_Type)的函数会引发"无法为内置/扩展类型设置属性"异常Py_TPFLAGS_HEAPTYPE.此标志设置为动态类型.你可以让你的类型动态,但它可能是更多的工作,那么它是值得的.

这意味着我最初给出的解决方案允许您在C类型对象上创建属性(如::computed属性),但限制为只读.对于设置,您可以使用类/ static-method(方法中的METH_CLASS/ METH_STATICflag tp_methods).