总结:
一:迭代器和可迭代对象
1:迭代器,凡是定义了__iter__和__next__方法的类都是迭代器;
2:可迭代对象,定义了__iter__或者__getitem__方法的类,叫做可迭代对象。
所以:迭代器一定是可迭代对象,但可迭代对象不一定是迭代器;
二:iter()和next()函数
iter(func)调用函数func的__iter__或者__getitem__函数,当__iter__返回的是迭代器时,则相当于iter()把一个可迭代对象转变为了迭代器,这一点后面细讲;
next(迭代器)调用迭代器的__next__内置函数;
参考自:(2条消息) Python迭代器基本方法iter()及其魔法方法__iter__()原理详解_涛涛ALG的博客-CSDN博客_iter迭代器
首先,先讲一下for循环的原理,for循环会优先进到类的__iter__函数中,然后再循环调用函数的__next__方法。如果类没有定义__iter__函数,则查找类是否有__getiten__函数,如果有__getiten__,则调用默认迭代器,从0开始产生索引,来调用__getitem__方法。
像python中的list,dict等,都是可迭代对象,但不是迭代器,也就是说,list,dict这些函数是有__iter__函数的,但是没有__next__函数,但是他们的__iter__函数的返回值是一个迭代器,也就是说它们的__iter__函数的返回值是一个具有__next__函数的迭代器。
参考:(24条消息) 解密 Python 迭代器的实现原理_python迭代器原理_passionSnail的博客-CSDN博客
上面说到,iter(func)会去调用func的__iter__或者__getitem__内置函数,为什么呢?我们看一下iter()的底层实现(引用自上诉参考):
static PyObject *
builtin_iter(PyObject *self,
PyObject *const *args, Py_ssize_t nargs)
{
PyObject *v;
// iter 函数要么接收一个参数, 要么接收两个参数
if (!_PyArg_CheckPositional("iter", nargs, 1, 2))
return NULL;
v = args[0];
// 如果接收一个参数
// 那么直接使用 PyObject_GetIter 获取对应的迭代器即可
// 可迭代对象的类型不同,那么得到的迭代器也不同
if (nargs == 1)
return PyObject_GetIter(v);
// 如果接收的不是一个参数, 那么一定是两个参数
// 如果是两个参数, 那么第一个参数一定是可调用对象
if (!PyCallable_Check(v)) {
PyErr_SetString(PyExc_TypeError,
"iter(v, w): v must be callable");
return NULL;
}
// 获取value(哨兵)
PyObject *sentinel = args[1];
//调用PyCallIter_New
//得到 calliterobject 对象
/*
该对象位于 Objects/iterobject.c 中
*/
return PyCallIter_New(v, sentinel);
}
可以看到,iter()函数的输入要么只有一个,要么输入两个; 输入是一个的时候,则直接调用 PyObject_GetIter函数,否则调用PyCallIter_New函数;我们以输入一个变量调用的PyObject_GetIter函数进行说明,PyObject_GetIter函数如下:
PyObject *
PyObject_GetIter(PyObject *o)
{
// 获取可迭代对象的类型对象
PyTypeObject *t = Py_TYPE(o);
// 我们说类型对象定义的操作,决定了实例对象的行为
// 实例对象调用的那些方法都是定义在类型对象里面的
// 所以obj.func()本质上就是type(obj).func(obj)的语法糖
getiterfunc f;
// 所以这里是获取类型对象的 tp_iter 成员
// 也就是 Python 中的 __iter__
f = t->tp_iter;
// 如果 f 为 NULL
// 说明该类型对象内部的tp_iter成员被初始化为NULL
// 即内部没有定义 __iter__
// 像str、tuple、list等类型对象,它们的tp_iter成员都是不为NULL的
if (f == NULL) {
// 如果 tp_iter 为 NULL,那么解释器会退而求其次
// 检测该类型对象中是否定义了 __getitem__
// 如果定义了,那么直接调用PySeqIter_New
// 得到一个seqiterobject对象
// 下面的PySequence_Check负责检测类型对象是否实现了__getitem__
if (PySequence_Check(o))
return PySeqIter_New(o);
// 走到这里说明该类型对象既没有__iter__、也没有__getitem__
// 因此它的实例对象不具备可迭代的性质,于是抛出异常
return type_error("'%.200s' object is not iterable", o);
}
else {
// 否则说明定义了__iter__,于是直接进行调用
// Py_TYPE(o)->tp_iter(o) 返回对应的迭代器
PyObject *res = (*f)(o);
// 但如果返回值res不为NULL、并且还不是迭代器
// 证明 __iter__ 的返回值有问题,于是抛出异常
if (res != NULL && !PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"iter() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
Py_DECREF(res);
res = NULL;
}
// 返回 res
return res;
}
}
可以看到PyObject_GetIter函数实际上就做了以下三件事:
1:判断iter()中传入的参数func是否有__iter__内置函数,如果有,直接返回这个内置参数
2:如果iter()中传入的参数func里面没有__iter__内置函数,则判断func里面是否有__getitem__函数,如果有,则返回这个__getitem__函数;
3:如果上面个两个都没有,则报错
未完待续.....
以下是目前自己的一些理解(胡说八道,便于自己理解):
iter()通过__iter__这个内置函数,将可迭代对象包装了起来,并加上了一个索引;而这个索引必须通过next()去获得,这也就是__iter__必须返回迭代器(包含__iter__和__next__函数的类就是迭代器)的原因。
通过next()获得这个索引,而这个索引指代的其实就是__next__这个内置函数,所以每一次next()相当于就是调用的__next__函数;理论上讲这个索引是没有上限的,也就是可以无限的next,但是在__next__里面可以设定终止条件;向list,tuple这些其实也是这样,内部根据自己的长度设置了终止条件