30-源码解密内置函数 iter、next 楔子 这次我们来看看 iter 和 next 这两个内置函数的用法,我们知道 iter 是将一个可迭代对象变成一个迭代器,next 是将迭代器里的值一步一步迭代出来。
1 2 3 4 5 6 lst = [1 , 2 , 3 ] it = iter (lst) print (it) print (next (it))
注意:iter 还有一个鲜为人知的用法,我们来看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 val = 0 def foo (): global val val += 1 return val for i in iter (foo, 5 ): print (i) """ 1 2 3 4 """
当然 next 函数也有一个特殊用法,就是它在接收一个迭代器的时候,还可以指定一个默认值;如果元素迭代完毕之后再次迭代的话,不会抛出StopIteration,而是会返回默认值。
1 2 3 4 5 6 it = iter ([1 , 2 , 3 ]) print (next (it)) print (next (it)) print (next (it)) print (next (it, "xxx" )) print (next (it, "yyy" ))
注意:iter 内部接收可迭代对象的类型不同,那么得到的迭代器种类也不同。
1 2 3 print (iter ("xyz" )) print (iter ((1 , 2 , 3 ))) print (iter ([1 , 2 , 3 ]))
iter 函数底层实现 我们看一下 iter 函数底层是如何实现的,其实之前已经见识过了,还想的起来吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static PyObject *builtin_iter (PyObject *self, PyObject *const *args, Py_ssize_t nargs) { PyObject *v; if (!_PyArg_CheckPositional("iter" , nargs, 1 , 2 )) return NULL ; v = args[0 ]; if (nargs == 1 ) return PyObject_GetIter(v); if (!PyCallable_Check(v)) { PyErr_SetString(PyExc_TypeError, "iter(v, w): v must be callable" ); return NULL ; } PyObject *sentinel = args[1 ]; return PyCallIter_New(v, sentinel); }
所以核心就在于 *PyObject_GetIter* 中,它是根据可迭代对象生成迭代器的关键,那么它都做了哪些事情呢?不用想肯定是执行:obj.__iter__()
,当然更准确的说应该是:type(obj).__iter__(obj)
,我们来看一下。该函数定义在 *Objects/abstract.c* 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 PyObject * PyObject_GetIter (PyObject *o) { PyTypeObject *t = Py_TYPE(o); getiterfunc f; f = t->tp_iter; if (f == NULL ) { if (PySequence_Check(o)) return PySeqIter_New(o); return type_error("'%.200s' object is not iterable" , o); } else { PyObject *res = (*f)(o); 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 ; } return res; } }
所以我们看到这便是 iter 函数的底层实现,里面我们提到了 __getitem__。我们说如果类型对象内部没有定义 __iter__,那么解释器会退而求其次检测内部是否定义了 __getitem__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class A : def __getitem__ (self, item ): return f"参数item: {item} " a = A() print (a["name" ]) print (a["夏色祭" ]) for idx, val in enumerate (a): print (val) if idx == 5 : break """ 参数item: 0 参数item: 1 参数item: 2 参数item: 3 参数item: 4 参数item: 5 """ class Girl : def __init__ (self ): self.names = ["夏色祭" , "神乐七奈" , "夜空梅露" , "雫_るる" ] def __getitem__ (self, item ): try : val = self.names[item] return val except IndexError: raise StopIteration g = Girl() for _ in g: print (_) """ 夏色祭 神乐七奈 夜空梅露 雫_るる """ print (list (g)) lst = [] lst.extend(g) print (lst)
以上被称为解释器的退化功能,就是在找不到某个实现的时候,会进行退化、尝试寻找其它实现。类似的做法还有其它,比如:
1 2 3 4 5 6 7 8 class A : def __len__ (self ): return 0 print (bool (A()))
如果内部定义了 __iter__,则直接调用即可。
1 2 3 4 5 print (str .__iter__("123" )) print (list .__iter__([1 , 2 , 3 ])) print (tuple .__iter__((1 , 2 , 3 ))) print (set .__iter__({1 , 2 , 3 }))
next函数底层实现 了解 iter 之后,我们再来看看 next 函数;如果内部定义了 next 函数,那么不用想,结果肯定是type(obj).__next__(obj)
,我们看一下底层实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static PyObject *builtin_next (PyObject *self, PyObject *const *args, Py_ssize_t nargs) { PyObject *it, *res; if (!_PyArg_CheckPositional("next" , nargs, 1 , 2 )) return NULL ; it = args[0 ]; if (!PyIter_Check(it)) { PyErr_Format(PyExc_TypeError, "'%.200s' object is not an iterator" , it->ob_type->tp_name); return NULL ; } res = (*it->ob_type->tp_iternext)(it); if (res != NULL ) { return res; } else if (nargs > 1 ) { PyObject *def = args[1 ]; if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_StopIteration)) return NULL ; PyErr_Clear(); } Py_INCREF(def); return def; } else if (PyErr_Occurred()) { return NULL ; } else { PyErr_SetNone(PyExc_StopIteration); return NULL ; } }
还是比较简单的,我们以 列表 对应的迭代器为例,举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 lst = [1 , 2 , 3 ] it = iter (lst) print (type (it)) print (type (it).__next__(it)) print (type (it).__next__(it)) print (type (it).__next__(it))
怎么样,是不是很简单呢?
我们看到一个变量 obj 不管指向什么可迭代对象,都可以交给 iter,得到对应的迭代器;不管什么迭代器,都可以交给 next 进行迭代。原因就在于它们接收的不是对象本身,而是对象对应的 PyObject * 泛型指针。不管你是谁的指针,只要你指向的对象是一个可迭代对象,那么都可以交给 iter。至于 next 也是同理,不管你指向的是哪一种迭代器,只要是迭代器,就可以交给 next,然后会自动调用迭代器内部的 __next__(底层是 tp_iternext)将值给迭代出来。所以这是不是相当于实现了多态呢?所以这就是 Python 的设计哲学,变量只是一个指针,传递变量的时候相当于传递指针(将指针拷贝一份),但是操作一个变量的时候会自动操作变量(指针)指向的内存。比如:a = 12345; b = a,相当于把 a 拷贝了一份给 b,但 a 是一个指针,所以此时 a 和 b 保存的地址是相同的,也就是指向了同一个对象。但 a + b 的时候则不是两个指针相加、而是将 a、b 指向的对象进行相加,也就是操作变量会操作变量指向的内存。因此在 Python 中,说传递方式是值传递或者引用传递都是不准确的,应该是****变量之间的赋值传递,对象之间的引用传递。