检查对象在python c扩展中是否可迭代

时间:2020-03-01 04:13:06

标签: python python-3.x python-c-api python-3.8

我需要编写一个函数,该函数根据python参数是否可迭代来执行不同的操作。这是一个代码示例:

PyObject *iter = PyObject_GetIter(arg); // arg is a PyObject*
if (iter) {
  // do iterable things
} else {
  // do non-iterable things
}

但是,如果arg不可迭代,则不仅iter == NULL还会出现异常。处理这种情况的正确方法是什么?我只是打电话给PyErr_Clear()并希望没有其他错误设置吗?

1 个答案:

答案 0 :(得分:1)

在纯Python中,找出对象是否可迭代的通常解决方案是调用iter(...)并查看会发生什么情况(例如,它被“ Fluent Python”流行):

def is_iterable(obj):
    try:
        iter(obj)  # ok, it worked
        return True
    except TypeError:
        return False

有关更多详细信息,请参见出色的answer

这基本上也是@falsetru在注释中的建议-如果PyObject_GetIter失败,则尝试清除错误:

int is_iterator(PyObject *obj){
    PyObject *it =  PyObject_GetIter(obj);
    if(it != NULL){
        Py_DECREF(it);
        return 1; // object can be iterated
    }
    else if (PyErr_ExceptionMatches(PyExc_TypeError)) {
        PyErr_Clear();
        return 0; // is not an iterator
    }
    else{
        return -1; // error
    }
}

但这并不是“可迭代”的意思,然后可以根据需要调整PyObject_GetIter的实现,例如:

int is_iterator2(PyObject *obj) {
    return Py_TYPE(obj)->tp_iter != NULL || PySequence_Check(obj);
}

与通常的算法一样,is_iterator2会查询是否存在tp_iter插槽(即__iter__函数),如果不存在,则会通过{退回到sequence protocol {1}}。但是,与第一个版本不同的是,未调用__getitem__槽,并且不检查其结果是否是迭代器,即用于

tp_iter

class C: def __iter__(self): raise BufferError() class D: def __iter__(self): return 1; # isn't iterator C()将被归类为可迭代(对于第一个D()版本则不是这种情况)。同样,如果is_iterator返回1,则并不意味着is_iterator2不返回PyObject_GetIter,如上述类所示。

相关问题