如何在自定义PyTypeObject中同时使用.tp_getattro / .tp_setattro和.tp_getset? .tp_getset已实现 .tp_getattro已实现 Custom_getattro() Custom_setattro()

问题描述

我正在努力将现有的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_getsetPyTypeObject

谢谢!

解决方法

感谢@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;
}