如何在自定义 PyTypeObject 中同时使用 .tp_getattro / .tp_setattro 和 .tp_getset?
How does one use both .tp_getattro / .tp_setattro and .tp_getset in a custom PyTypeObject?
我正在努力将现有的 C 应用程序扩展到包含自定义数据类型的 Python。我希望用户能够将任意字符串作为此自定义类型对象的属性传递。一些特殊属性将始终存在并具有已知行为(例如 CustomObject()._ndim
)。对象中可能存在也可能不存在任何其他字符串,并且需要额外处理(例如 CustomObject().asdf
)。
为了解决这个问题并尽可能接近标准 API,我想利用 .tp_getattro
/ .tp_setattro
和 [= PyObjectType 结构的 19=] 特征。 .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
# ...
如何在自定义 PyTypeObject
中同时使用 .tp_getattro
/ .tp_setattro
和 .tp_getset
?
谢谢!
感谢@DavidW 在对问题的评论中,我找到了一个解决方案:分别是 .tp_getattro
/ .tp_setattro
functions must call 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, * value, * traceback;
// First try to set a dynamic attribute.
rc = _set_custom(o, attr_name);
// If that was unsuccessful, set an attribute in our .tp_dict
if (rc != 0) {
PyErr_Fetch(&type, &value, &traceback);
rc = PyObject_GenericSetAttr(o, attr_name, v);
// Use the original error, if necessary
if (rc != 0) {
PyErr_Restore(type, value, traceback);
} else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
return rc;
}
我正在努力将现有的 C 应用程序扩展到包含自定义数据类型的 Python。我希望用户能够将任意字符串作为此自定义类型对象的属性传递。一些特殊属性将始终存在并具有已知行为(例如 CustomObject()._ndim
)。对象中可能存在也可能不存在任何其他字符串,并且需要额外处理(例如 CustomObject().asdf
)。
为了解决这个问题并尽可能接近标准 API,我想利用 .tp_getattro
/ .tp_setattro
和 [= PyObjectType 结构的 19=] 特征。 .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
# ...
如何在自定义 PyTypeObject
中同时使用 .tp_getattro
/ .tp_setattro
和 .tp_getset
?
谢谢!
感谢@DavidW 在对问题的评论中,我找到了一个解决方案:分别是 .tp_getattro
/ .tp_setattro
functions must call 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, * value, * traceback;
// First try to set a dynamic attribute.
rc = _set_custom(o, attr_name);
// If that was unsuccessful, set an attribute in our .tp_dict
if (rc != 0) {
PyErr_Fetch(&type, &value, &traceback);
rc = PyObject_GenericSetAttr(o, attr_name, v);
// Use the original error, if necessary
if (rc != 0) {
PyErr_Restore(type, value, traceback);
} else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
return rc;
}