使用Python C-API的类属性

使用Python C-API的类属性,python,python-c-api,Python,Python C Api,使用PythonC-API创建类属性(as和)的最佳方法是什么?静态属性也适用于我的情况 跟进: 我试着执行牦牛的建议。我定义了一个类P,在其tp\u descr\u get和tp\u descr\u set插槽中使用get和set函数。然后,我在P键下为类X的type对象的字典中添加了一个P的实例。然后 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 prin

使用PythonC-API创建类属性(as和)的最佳方法是什么?静态属性也适用于我的情况

跟进:

我试着执行牦牛的建议。我定义了一个类
P
,在其
tp\u descr\u get
tp\u descr\u set
插槽中使用get和set函数。然后,我在
P
键下为类
X
的type对象的字典中添加了一个
P
的实例。然后

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张打印三次,然后11张打印三次),但是

失败并显示错误消息

TypeError: can't set attributes of built-in/extension type 'X'
我该如何解决这个问题

跟进2:

如果我用
PyMem\u Malloc
分配type对象,并设置
Py\u TPFLAGS\u HEAPTYPE
标志,那么一切都正常;我可以用预期的结果做
X.p=12

如果我将type对象保存在一个静态变量中,并设置
Py\TPFLAGS\uheaptype
标志,事情也会发生,但这显然不是一个好主意。(但为什么类型对象在静态内存还是动态内存中很重要呢?我从来没有让它的引用计数降到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&) {}
}
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\u descr\u get
函数(对应于Python中的
\uu get\uu

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

跟进:

似乎不可能设置C类型的属性。元类(
PyType\u Type
)的
tp\u setattro
函数为所有非堆类型(没有
Py\u TPFLAGS\u HEAPTYPE
标志的类型)引发“无法设置内置/扩展类型的属性”异常。此标志是为动态类型设置的。您可以使您的类型动态,但这可能需要更多的工作,然后它的价值


这意味着我最初给出的解决方案允许您在C类型对象上创建一个属性(如:computed attribute),但限制为只读。对于设置,您可以使用类/静态方法(
tp\u方法中的方法上的
METH\u class
/
METH\u static
标志)。

如果这是一个可接受的解决方案,您可以在模块上创建一个包含
X
声明的方法,该方法只需按照您的意愿设置类变量。例如:

PyObject* set_p_value(PyObject*, PyObject* o) {
  if(PyDict_SetItemString(X_type()->tp_dict, "p", o) == -1) return 0;
  Py_RETURN_NONE; 
}

PyMethodDef methods[] = 
{
    ...
    {"set_p_value", (PyCFunction)set_p_value, METH_O, 0},
    {0, 0, 0, 0}
};
一旦存在,则:

from M import X, set_p_value
set_p_value(3)
print X.p #should print '3'

应该像预期的那样工作。遗憾的是,这种功能与类型对象本身无关。如果您提供了一个类方法,可以根据自己的意愿设置类变量,则可以部分避免这种情况。

我将尝试传达我发现的关于使用类静态属性的本质

我的(编辑)代码如下:

//准备类型对象,毫无疑问您将完成它
//通过调用PyType_Ready,如下所示。
如果(PyType_就绪(typeObj)<0)
{
返回;
}
Py_增量(类型OBJ);
PyModule_AddObject(module,typeName,(PyObject*)typeObj);
//现在我们将静态成员直接添加到类型的tp_dict中,但是
//只有在我们注册了类型之后(如上所述)
PyObject*dict=typeObj->tp_dict;
//您将在下面的行中有自己的包装器/C值。这只是
//我(成功)使用的东西的伪版本。
PyObject*tmp=MyCreateWrapper(myStaticValueInC);
//Py_增量(tmp);//您可能需要这个,这取决于上面这条线的工作方式。
PyDict_SetItemString(dict、staticPropertyName、tmp);
Py_DECREF(tmp);

我相信这里的所有要点都是关于为了实现类属性而构造代码的顺序。

谢谢!我试过了,几乎成功了;请看编辑后的问题。谢谢!我试着使用动态类型,它很有效;查看编辑后的问题。您可以发布到目前为止的内容吗?结果表明,设置类型属性时不会调用
tp\u descr\u set
,它仅对实例有效。解决方法是将描述符移动到元类型。您的类型将是元类型的一个实例,所以描述符可以工作。您可以将元类型设置为
X
ob\u type
。所有这些都同样适用于Python,请参见以下()示例。如果在
X
的实例上而不是在类型本身上获取/设置
p
是一个选项,那么事情就会变得简单得多,因为您可以使用
tp\u getset
。请检查这一点以获得Guido的解释:
from M import X, set_p_value
set_p_value(3)
print X.p #should print '3'