在 PyGetSetDef 中使用闭包来重用属性 getter-setter
Posted
技术标签:
【中文标题】在 PyGetSetDef 中使用闭包来重用属性 getter-setter【英文标题】:Use closure in PyGetSetDef for reusing attribute getter-settter 【发布时间】:2021-12-28 15:02:29 【问题描述】:正如标题所示,我正在尝试创建一堆属性,但代码变得重复且混乱。我想使用closure
参数使代码更紧凑。
根据C API reference,闭包是一个函数指针,它为getter/setter 提供了额外的信息。我还没有找到它的使用示例。
这是我目前使用它的方式:
static void closure_1() ;
static void closure_2() ;
...
static PyObject *
FOO_getter(FOO* self, void *closure)
if (closure == &closure_1)
return self->bar_1;
else if (closure == &closure_2)
return self->bar_2;
static int
FOO_setter(FOO* self, PyObject *value, void *closure)
if (closure == &closure_1)
if (somehow value is invalid)
PyErr_SetString(PyExc_ValueError, "invalid value for bar_1.");
return -1;
else if (closure == closure_2)
if (somehow value is invalid)
PyErr_SetString(PyExc_ValueError, "invalid value for bar_2.");
return -1;
return 0;
static PyGetSetDef FOO_getsetters[] =
"bar_1", (getter) FOO_getter, (setter) FOO_setter, "bar_1 attribute", closure_1,
"bar_2", (getter) FOO_getter, (setter) FOO_setter, "bar_2 attribute", closure_2,
NULL /* Sentinel */
;
...
它按照我想要的方式工作,但它看起来更像是一个 hack,而不是“pythonic”。有没有更好的方法来处理这个?例如以某种方式调用闭包。
【问题讨论】:
【参考方案1】:我猜这个“闭包”是用来将额外的上下文传递给Foo_getter
。它应该可以简化访问Foo
的成员。文档可能是错误的。它应该是“可选指针”,而不是“可选函数指针”。
考虑传递成员的偏移量。使用stddef.h
中定义的标准offsetof 宏可以轻松获得结构成员的偏移量。它是一个小的无符号整数,适合void*
类型。
static PyGetSetDef FOO_getsetters[] =
"bar_1", (getter) FOO_getter, (setter) FOO_setter, "bar_1 attribute", (void*)offsetof(FOO, bar_1),
"bar_2", (getter) FOO_getter, (setter) FOO_setter, "bar_2 attribute", (void*)offsetof(FOO, bar_2),
NULL /* Sentinel */
;
现在吸气剂可能是:
static PyObject *
FOO_getter(FOO* self, void *closure)
// pointer to location where the FOO's member is stored
char *memb_ptr = (char*)self + (size_t)closure;
// cast to `PyObject**` because `mem_ptr` points to location where a pointer to `PyObject` is stored
return *(PyObject**)mem_ptr;
对 setter 使用类似的架构。
【讨论】:
相当整洁。闭包是任何类型的指针似乎都是合乎逻辑的。【参考方案2】:尽管有文档,我假设closure
可以是您想要的任何指针。那么传递一个“对象”怎么样,因为 C 不支持闭包(缺少literally generating functions at run-time)。
在对象中,我们可以将成员的偏移量存储在FOO
中,以及指向特定属性验证器的指针。
typedef int (*Validator)(FOO *, const struct Attribute *, void *);
typedef struct Attribute
const char *name;
size_t offset;
Validator validator;
Attribute;
static PyObject **resolve_offset(FOO *self, const Attribute *attr)
return (PyObject **)( ( (char *)self ) + attr->offset );
static PyObject *FOO_getter(FOO *self, void *_attr)
const Attribute *attr = (const Attribute *)_attr;
return *resolve_offset(self, attr);
static int FOO_setter(FOO *self, PyObject *val, void *_attr)
const Attribute *attr = (const Attribute *)_attr;
if (attr->validator(self, attr, val))
*resolve_offset(self, attr) = val;
return 0;
else
// Building the string to include attr->name is left to you.
PyErr_SetString(PyExc_ValueError, "invalid value.");
return -1;
static int FOO_bar_1_validator(FOO *self, const Attribute *attr, void *val) ...
static int FOO_bar_2_validator(FOO *self, const Attribute *attr, void *val) ...
#define ATTRIBUTE(name) \
static Attribute FOO_ ## name ## attr = \
#name, \
offsetof(FOO, name), \
FOO_ ## name ## _validator \
;
ATTRIBUTE(bar_1);
ATTRIBUTE(bar_2);
#define PY_ATTR_DEF(name) \
#name, \
(getter)FOO_getter, \
(setter)FOO_setter, \
#name " attribute", \
&(FOO_ ## name ## attr) \
static PyGetSetDef FOO_getsetters[] =
PY_ATTR_DEF(bar_1),
PY_ATTR_DEF(bar_2),
NULL
;
我原来是这样写的:
resolve_offset
肯定依赖于未定义的行为,但它应该可以正常工作。另一种方法是在我们的属性对象中使用三个函数(get、validate、set)而不是一个,但这违背了问题的重点。
但@tstanisl 指出它看起来像it isn't UB。太棒了!
【讨论】:
我不确定它是否是 UB。最近我发布了一个关于它的问题。见***.com/q/69771966/4989451。到目前为止,共识是定义resolve_offset
。
@tstanisl,太棒了!我倾向于认为所有奇怪的东西都是 UB :)
有趣的方法。 tstanisl 的方法对我来说效果更好,特别是因为我的 Foo
结构中只有基本的 C 类型。
@M47,您的版本中有每个属性验证器。他们和我的唯一区别是我的有这些验证器,而他们的没有。以上是关于在 PyGetSetDef 中使用闭包来重用属性 getter-setter的主要内容,如果未能解决你的问题,请参考以下文章