python-for循环原理:遍历的可迭代对象

551 阅读4分钟

(一)内置函数iter()返回迭代器对象

docs.python.org/3/library/s…

iter(object[, sentinel])——返回一个iterator对象

  • 如果没有给定第二个参数sendinel,则第一个参数object必须是支持迭代协议(__iter __()方法)的集合对象,或者它必须支持序列协议(整数参数从0开始的__getitem __()方法)。 如果它不支持这些协议中的任何一个,则会引发TypeError。 (下面主要围绕这种情况展开)
  • 如果给定第二个参数sendinel,则第一个参数object必须是可调用对象。 此时,iter 创建了一个迭代器对象,每次调用这个迭代器对象的__next__()方法时,都会调用 object(可调用对象)。 如果返回的值等于sendinel,则将引发StopIteration,否则将返回该值。

内置函数iter()源码如下,也就是获取object参数的迭代器对象是通过PyObject_GetIter方法获得的。

static PyObject *builtin_iter(PyObject *self, PyObject *args)
{
    PyObject *v, *w = NULL;

    if (!PyArg_UnpackTuple(args, "iter", 1, 2, &v, &w))  //[1]传入的参数args是元组,长度min=1,max=2,args[0]填充变量v,arg[1]填充变量w
        return NULL;
    if (w == NULL)
        return PyObject_GetIter(v);  //[2]arg[0]是iter(object[, sentinel])中的object参数
    if (!PyCallable_Check(v)) {
        PyErr_SetString(PyExc_TypeError,
                        "iter(v, w): v must be callable");
        return NULL;
    }
    return PyCallIter_New(v, w);
}

PyArg_UnpackTuple用法说明:docs.python.org/2/c-api/arg…

(二)for循环如何获得对象的迭代器

由for循环的[GET_ITER]代码段可知,**遍历过程中通过PyObject_GetIter方法获取迭代器对象,和iter()内置函数使用的是一样的方法。**下面是PyObject_GetIter方法代码:

PyObject* PyObject_GetIter(PyObject *o){
    PyTypeObject *t=o->ob_type;
    getiterfunc f=NULL;
    if(PyType_HasFeature(t,PY_TPFLAGS_HAVE_ITER))
        //获得类型对象中的tp_iter操作
        f=t->tp_iter;
    if(f==NULL){
        ...
    }else{
        //通过tp_iter操作获得iterator
        PyObject *res=(*f)(o);
        ...
        return res;
    }
}

从上面代码可以看出,PyObject_GetIter方法是通过调用对象(比如PyLisyObject)对应的的类型对象(比如PyList_Type)的tp_iter域获取迭代器的,类型对象PyList_Type的tp_iter域被设置成list_iter方法(juejin.cn/post/686413…)。当我们自定义迭代器对象时,定义__iter__()方法,实则就是将tp_iter域设置成__iter__()。

(三)定义迭代器(iterator)对象

迭代器对象需要实现迭代器协议的两个方法

iterator.__iter__():返回迭代器对象本身。

这个方法是使用for和in语句遍历容器或迭代器对象所必需的, 它对应于Python / C API中Python对象的类型结构的tp_iter插槽。(对于列表,tp_iter设置为list_iter。)

iterator.__next__():返回容器的下一个元素,如果没有,则引发StopIteration异常

** 这个方法对应于Python / C API中Python对象的类型结构的tp_iternext插槽。**(对于列表,p_iternext设置为listiter_next。)

这两个方法,就是整个for循环的关键方法,以下等同:

lst=[1,2,3,4,5]
for x in lst:
    print(x)

lst=[1,2,3,4,5]
it=iter(lst)
while True:
    try:
        x=next(it)
        print(x)
    except StopIteration:
        break

(四)如何定义能够被for循环遍历的对象

for和in语句和iter()都用同样的方法获取迭代器,所以可以认为入参也相同。也就是,for循环可以遍历的是支持迭代协议(__iter __()方法)的集合对象,或者它必须支持序列协议(整数参数从0开始的__getitem __()方法)的对象——可迭代(iterable)对象。

可迭代对象:docs.python.org/3/glossary.…

(1)支持迭代协议:生成器(generators)、file objects、集合、dict.keys(), dict.values() and dict.items();

(2)支持序列协议:列表、元组、字符串、range

(3)自定义对象:实现可迭代协议(__iter __()方法)或序列协议(__getitem __()方法)

在定义对象时定义__iter__()就可实现可迭代对象。但是要能被for和in语句遍历。

1、__iter__()方法返回一个包含__next__()方法的对象

2、__iter__()方法返回self,同时定义__next__()方法。也就是对象本身既是可迭代对象也是迭代器。

对于方法2,如果不定义__next__()方法,在调用iter()方法/for循环获取迭代器会报错“TypeError: iter() returned non-iterator of type '类名‘”,我觉得这里应该和PyObject_GetIter方法中"PyType_HasFeature(t,PY_TPFLAGS_HAVE_ITER)"这段代码有关。

  • 方法1实现for循环遍历对象

TraverseIterator有__iter__()方法时,print(isinstance(TraverseIterator(self.data), Iterator))输出结果是True,否则输出结果时False。

不过即便没有TraverseIterator没有实现__iter__()方法,也就是没有注释[2]处的代码,依然可以被for循环所遍历。

  • 方法2实现for循环遍历对象: