问题描述
我正在努力将现有的C应用程序扩展到包含自定义数据类型的Python中。我希望用户能够将任意字符串作为此自定义类型的对象的属性传递。一些特殊属性将始终存在并具有已知的行为(例如CustomObject()._ndim
)。对象中可能存在也可能不存在任何其他字符串,并且需要进行其他处理(例如CustomObject().asdf
)。
要解决此问题并尽可能与标准API保持紧密联系,我想利用.tp_getattro
/ .tp_setattro
和.tp_getset
功能的优势PyObjectType结构。 .tp_getset
提供了一个干净的界面,用于定义getter和setter,文档,并自动以dir()
结尾。 .tp_getattro
/ .tp_setattro
是提供动态功能所必需的。
但是,如果两者都作为PyTypeObject实例提供,则由于.tp_getset
/ .tp_getattro
已被覆盖,因此结果类型将忽略.tp_setattro
函数。我假设有一个电话或简短的节,需要添加到我的.tp_getattro
/ .tp_setattro
实现中才能实现。
到目前为止,我已经尝试在调用*attro()
覆盖时回溯GDB。我看到调用堆栈中有一个拆分,具体取决于所实现的哪个,表明涉及PyObject_GenericGetAttr()。
.tp_getset
已实现
[0] from 0x00007ffff74700d6 in Info_get_ndim+17 at info.c:396
[1] from 0x00000000004c4419 in getset_get+185 at ../Objects/typeobject.c:1399
[2] from 0x000000000059b20f in _PyObject_GenericGetAttrWithDict+730 at ../Objects/typeobject.c:3125
[3] from 0x000000000059b20f in PyObject_GenericGetAttr+730 at ../Objects/object.c:1306
[4] from 0x000000000059b20f in PyObject_GetAttr+799 at ../Objects/object.c:912
[5] from 0x0000000000541fb5 in _PyEval_EvalFrameDefault+1077 at ../Python/ceval.c:2573
# ...
.tp_getattro
已实现
[0] from 0x00007ffff74700d6 in Info_getattro+17 at info.c:185
[1] from 0x000000000059b20f in PyObject_GetAttr+434 at ../Objects/dictobject.c:1333
[2] from 0x0000000000541fb5 in _PyEval_EvalFrameDefault+1077 at ../Python/ceval.c:2573
# ...
如何在自定义.tp_getattro
中同时使用.tp_setattro
/ .tp_getset
和PyTypeObject
?
谢谢!
解决方法
感谢@DavidW在问题注释中,我遇到了一个解决方案:.tp_getattro
/ .tp_setattro
函数必须分别调用PyObject_GenericGetAttr()
/ PyObject_GenericSetAttr()
。
为什么这不会自动发生很有意义:现在您有了更多的控制权。您可以控制要优先使用的属性,如果属性不存在该怎么办。就我而言,我希望动态属性具有优先级,并且我不希望用户能够添加自己的属性。所以我的*attro()
看起来像这样:
Custom_getattro()
static PyObject * Custom_getattro(PyObject * o,PyObject * attr_name) {
CustomObject * s = (CustomObject *) o;
PyObject * res,* type,* value,* traceback;
// First try to get a dynamic attribute
res = _get_custom(s,attr_name);
// If that was unsuccessful,get an attribute out of our .tp_dict
if (! res) {
PyErr_Fetch(&type,&value,&traceback);
res = PyObject_GenericGetAttr(o,attr_name);
// Use the original error,if necessary
if (! res) {
PyErr_Restore(type,value,traceback);
} else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
return res;
}
Custom_setattro()
static int32_t Custom_setattro(PyObject * o,PyObject * attr_name,PyObject * v) {
int32_t rc = -1;
CustomObject * s;
PyObject * type,* traceback;
// First try to set a dynamic attribute.
rc = _set_custom(o,set an attribute in our .tp_dict
if (rc != 0) {
PyErr_Fetch(&type,&traceback);
rc = PyObject_GenericSetAttr(o,attr_name,v);
// Use the original error,if necessary
if (rc != 0) {
PyErr_Restore(type,traceback);
} else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
return rc;
}