2012-04-15 43 views
4

使用Python C-API創建類屬性(如herehere)的最佳方法是什麼?靜態屬性也適用於我的情況。使用Python C-API的類屬性

追問:

我試圖執行犛牛的建議。我在tp_descr_gettp_descr_set插槽中定義了一個類P,其中包含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印刷三次,然後11印製三次),但

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;它實際上將對象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 
+0

選中此項以獲得Guido的解釋:http://mail.python.org/pipermail/python-dev/2008-February/077167.html – yak 2012-04-16 13:09:47

回答

4

類似這些Python的解決方案,你將不得不在C創建一個classproperty類型和實施其tp_descr_get功能(相當於__get__在蟒蛇)。然後,如果要在C類型中使用該類型,則必須創建classproperty類型的實例並將其插入到類型的字典中(tp_dict您的類型的插槽)。

追問:

這似乎是不可能的設置C類的屬性。元類(PyType_Type)的tp_setattro函數爲所有非堆類型(沒有Py_TPFLAGS_HEAPTYPE標誌的類型)引發「無法設置內置/擴展類型的屬性」例外。該標誌設置爲動態類型。你可以使你的類型動態,但它可能是更多的工作,那麼它是值得的。

這意味着我最初給出的解決方案允許您在C類型對象上創建一個屬性(如在computed屬性中),但限制它是隻讀的。對於設置,您可以使用類/靜態方法(METH_CLASS/METH_STATIC標誌在tp_methods中的方法)。

+0

謝謝!我試過了,它幾乎可以工作。看到編輯的問題。 – user763305 2012-04-16 07:04:03

+0

謝謝!我嘗試使用動態類型,它的工作原理;看到編輯的問題。 – user763305 2012-04-16 11:11:24

+0

不,我錯了;這是行不通的;看到編輯的問題。 – user763305 2012-04-16 18:39:25

1

如果這是一個可接受的解決方案,您可以在模塊上創建一個方法,該模塊持有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' 

應該正常工作。不幸的是,這種功能與類型對象本身無關。如果您提供了一個可以根據需要設置類變量的類方法,則可以部分繞過該方法。

1

我會盡力傳達我使用類靜態屬性發現的內容。

我(編輯),代碼如下:

// 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); 

我相信所有的要領是在這裏的什麼順序來構建,以實現一個類的屬性代碼方面。