Python中range与enumerate调用next()的行为差异原因
这个差异的核心是Python中*可迭代对象(Iterable)和迭代器(Iterator)*是两套独立的实现协议,range属于纯可迭代对象,enumerate的返回值本身就是迭代器,二者对next()的响应逻辑自然不同。
两个协议的核心规则
- 所有能被
for循环遍历的对象都属于可迭代对象,它只需要实现__iter__()方法,该方法的作用是返回一个独立的迭代器实例。 - 迭代器是专门承载遍历逻辑的对象,它在可迭代对象的基础上额外实现了
__next__()方法:每次调用返回下一个遍历值,遍历到末尾就抛出StopIteration异常;同时迭代器的__iter__()方法要求返回自身,不会生成新的实例。 - 内置函数
next()的逻辑非常直接:直接调用传入对象的__next__()方法,如果对象没有实现这个方法,就会抛出TypeError。
range对象的行为逻辑
range是代表不可变整数序列的可迭代对象,定位和列表、字符串、字典这类容器完全一致:它需要支持多次重复遍历,每次遍历都从序列起始位置开始,因此它本身不会存储任何遍历状态,自然也不会实现__next__()方法。
每次对range调用iter(),都会生成一个全新的、独立的迭代器,各自维护自己的遍历指针,多个遍历操作之间完全互不干扰:
r = range(3) iter1 = iter(r) iter2 = iter(r) print(next(iter1)) # 输出0 print(next(iter1)) # 输出1 print(next(iter2)) # 输出0,两个迭代器的位置完全独立
直接对range本身调用next()时,因为找不到__next__方法,就会抛出你看到的TypeError: 'range' object is not an iterator错误。
enumerate对象的行为逻辑
enumerate()是内置的迭代器生成函数,它的返回值本身就是一个已经绑定了源可迭代对象、自带遍历状态的迭代器实例:既实现了返回自身的__iter__()方法,也实现了__next__()方法,因此可以直接传给next()调用。
对enumerate对象调用iter()拿到的就是它自己,不会生成新的迭代器实例,所有遍历操作共享同一个位置指针:
e = enumerate('ab') e_iter = iter(e) print(e is e_iter) # 输出True,iter返回的就是对象本身 print(next(e)) # 输出(0, 'a') print(next(e_iter)) # 输出(1, 'b'),遍历位置是连续的
也正因为这个设计,enumerate对象是一次性消耗品,遍历一次就会耗尽,没法像range那样反复从头遍历。
除了
enumerate之外,zip、map、filter等内置高阶函数的返回值也都是迭代器,都可以直接被next()调用;而列表、字符串、字典这类容器对象和range一样属于纯可迭代对象,必须先调用iter()拿到独立迭代器之后,才能用next()遍历。
内容的提问来源于stack exchange,提问作者apostofes

