【问题标题】:Class property using Python C-API使用 Python C-API 的类属性
【发布时间】:2012-04-27 01:03:34
【问题描述】:

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

跟进:

我尝试执行 yak 的建议。我在其tp_descr_gettp_descr_set 插槽中定义了一个类P 和get 和set 函数。然后我将P 的实例添加到X 类的类型对象字典中,键为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 打印 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 绑定到名称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 python-c-api


【解决方案1】:

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

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

跟进:

似乎不可能设置 C 类型的属性。元类 (PyType_Type) 的 tp_setattro 函数针对所有非堆类型(没有 Py_TPFLAGS_HEAPTYPE 标志的类型)引发“无法设置内置/扩展类型的属性”异常。该标志是为动态类型设置的。你可以让你的类型动态化,但它可能比它更有价值。

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

【讨论】:

  • 谢谢!我试过了,它几乎可以工作;查看已编辑的问题。
  • 谢谢!我尝试使用动态类型并且它有效;查看已编辑的问题。
  • 你能发布你目前所拥有的吗?
  • 原来tp_descr_set 在设置类型属性时不会被调用,它只适用于实例。一种解决方法是将描述符移动到元类型。您的类型将是元类型的实例,因此描述符将起作用。您可以将元类型设置为ob_typeX。所有这些也适用于 Python,例如,请参阅此 (python.6.n6.nabble.com/…)。
  • 如果在X 的实例上而不是在类型本身上获取/设置p 是一种选择,那么事情会变得非常简单,因为您可以使用@987654338 @.
【解决方案2】:

我将尝试传达我发现的关于使用类静态属性的精髓。

我的(修改后的)代码如下:

// Prepare your type object, which you will no doubt complete 
// by calling PyType_Ready, as here.
if (PyType_Ready(typeObj) < 0)
{
  return;
}

Py_INCREF(typeObj);
PyModule_AddObject(module, typeName, (PyObject*) typeObj);

// Now we add static members directly to the type's tp_dict, but 
// only *after* we've registered the type (above)
PyObject* dict = typeObj->tp_dict;

// You'll have your own wrapper / C values in the line below. This is just
// a pseudo-version of what I am (successfully) using.
PyObject* tmp = MyCreateWrapper(myStaticValueInC);

// Py_INCREF(tmp); // You may need this, depending on how line above works.

PyDict_SetItemString(dict, staticPropertyName, tmp);

Py_DECREF(tmp);

我相信关于构建代码以实现类属性的顺序的所有要点都在这里。

【讨论】:

  • 以这种方式修改 tp_dict appears to be prohibited:“警告使用 PyDict_SetItem() 或使用字典 C-API 修改 tp_dict 是不安全的。”
【解决方案3】:

如果这是一个可接受的解决方案,您可以在包含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'

应该按预期工作。一个缺点是,不幸的是,这个功能与类型对象本身无关。如果您提供了一个根据需要设置类变量的类方法,则可以部分避免这种情况。

【讨论】:

  • 以这种方式修改 tp_dict appears to be prohibited:“警告使用 PyDict_SetItem() 或使用字典 C-API 修改 tp_dict 是不安全的。”
【解决方案4】:

您可以向tp_dict 添加项目,只要它发生在您调用PyType_Ready 之前:

int err;
PyObject *p;

if (!X_Type.tp_dict) {
    X_Type.tp_dict = PyDict_New();
    if (!X_Type.tp_dict)
        return NULL;
}

p = PyLong_FromLong(12);
if (!p)
    return NULL;

err = PyDict_SetItemString(X_Type.tp_dict, "p", p)
Py_DECREF(p);
if (err)
    return NULL;

m = PyModule_Create(&M_module);
if (!m)
    return NULL;

if (PyModule_AddType(m, &X_type));
    return NULL;

return m;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-04
    • 2016-07-08
    • 2018-05-18
    • 2016-07-20
    • 1970-01-01
    • 1970-01-01
    • 2020-10-07
    相关资源
    最近更新 更多