测试C对象的setter /getter方法



我正在用C编写一个带有一组setter和getter的Python对象。我想限制通过setter/getter访问的变量的类型,并在传递不正确的类型时引发TypeError。例如,class_instance.minute = "string"应该引发TypeError,因为minute期望一个整数:

int
Event_set_min(EventObject *self, PyObject *val, void *closure)
{
VERIFY_NUMBER(val);
self->e->time->tm_min = (int) PyLong_AsLong(val);
return 0;
}

VERIFY_NUMBER宏展开为:

#define VERIFY_NUMBER(val) 
if (val == NULL) {                                                        
PyErr_SetString(PyExc_TypeError, "Cannot set to NULL");           
return -1;                                                        
} else if (!val->ob_type->tp_as_number) {                                 
PyErr_SetString(PyExc_TypeError, "Cannot set to non-number type); 
return -1;                                                        
}

如果我实例化对象并运行instance.minute = "string"(或None等),我会得到一个TypeError。我使用以下代码进行测试:

with self.assertRaises(TypeError):
a.minute = "string"
这给了我一个AttributeError和以下跟踪:
Traceback (most recent call last):
File "/home/lincoln/Documents/disorg/./test.py", line 38, in test_minute
a.minute = "abc"
File "/usr/lib/python3.9/unittest/case.py", line 226, in __exit__
self._raiseFailure("{} not raised".format(exc_name))
AttributeError: '_AssertRaisesContext' object has no attribute '_raiseFailure'

我做错了什么?谢谢!

您的宏VERIFY_NUMBER不正确。您应该使用PyLong_Check

如果其参数是PyLongObject或的子类型则返回truePyLongObject。这个函数总是成功的。

我会重写你的宏,并把它变成一个新的函数,像这样:

static int Verify_Number(PyObject *value)
{
int rc = 1;
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError, "should be int");
rc = 0;
}
return rc;
}

然后从你的函数Event_set_min:

中调用它
int Event_set_min(EventObject *self, PyObject *val, void *closure)
{
int rc = -1;
if (!Verify_Number(val)) {
return rc;
}
Py_INCREF(val);
Py_XDECREF(self->e->time->tm_min);
self->e->time->tm_min = val;
return 0;
}

请注意,我已经添加了Py_INCREFPy_XDECREF调用来增加/减少对象的引用计数-这将避免内存泄漏。


编辑:

正如在评论中指出的那样,为了尊重__index__协议,使用PyLong_AsLong并检查PyErr_Occurred是否引发错误可能更好。

所以这里有一个替代的解决方案:

#include <Python.h>
/*
* class Event:
*     def __new__(cls, *a, **kw):
*         event_obj = object.__new__(cls)
*         event_obj._minute = 0
*         return event_obj
*
*     def __init__(self, minute=None):
*         if event and isinstance(event, 'int'):
*             self._minute = minute
*
*     @property
*     def minute(self):
*         return self._minute
*
*     @minute.setter
*     def minute(self, value):
*         if not value or not isinstance(value, int):
*             raise TypeError("value should be int")
*         self._minute = value
*/

typedef struct {
PyObject_HEAD
int minute;
} EventObject;
static void
Event_dealloc(EventObject *self)
{
Py_TYPE(self)->tp_free((PyObject *) self);
}
static PyObject *
Event_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
int rc = -1;
EventObject *self = NULL;
self = (EventObject *) type->tp_alloc(type, 0);
if (self) {
self->minute = 0;
rc = 0;
}
if (rc < 0) {
Py_XDECREF(self);
}
return (PyObject *) self;
}
static int
Event_init(EventObject *self, PyObject *args, PyObject *kw)
{
int rc = -1;
static char *keywords[] = {"minute", NULL};
if (PyArg_ParseTupleAndKeywords(args, kw,
"|O", keywords,
&self->minute))
{
rc = 0;
}
return rc;
}
static PyObject *
Event_getminute(EventObject *self, void *closure)
{
return PyLong_FromLong(self->minute);
}
static int Verify_Number(PyObject *value, int *real_val)
{
int rc = 1;
int minute = PyLong_AsLong(value);
if (minute == -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError, "value should be int");
rc = 0;
}
else {
*real_val = minute;
}
return rc;
}
static int
Event_setminute(EventObject *self, PyObject *value, void *closure)
{
int rc = -1;
int minute = 0;
if (!Verify_Number(value, &minute)) {
return rc;
}
self->minute = minute;
return 0;
}
static PyGetSetDef Event_getsetters[] = {
{"minute", (getter)Event_getminute, (setter)Event_setminute}
};
static PyTypeObject EventType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "foo.Event",
.tp_doc = "Event objects",
.tp_basicsize = sizeof(EventObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Event_new,
.tp_init = (initproc) Event_init,
.tp_dealloc = (destructor) Event_dealloc,
.tp_getset = Event_getsetters,
};
static PyModuleDef module = {
PyModuleDef_HEAD_INIT, "foo", NULL, -1, NULL
};
PyMODINIT_FUNC
PyInit_foo(void)
{
PyObject *m = NULL;
if (PyType_Ready(&EventType) < 0)
return NULL;
if ((m = PyModule_Create(&module)) == NULL)
return NULL;
Py_XINCREF(&EventType);
PyModule_AddObject(m, "Event", (PyObject *) &EventType);
return m;
}