简评: 迭代器 (iterator) 是惰性可迭代对象(lazy iterable),range 函数在 Python 3 中是一个惰性的可迭代对象, 那么 range 是不是迭代器呢? 为什么
TLNR:Python 3 中的 range 对象 (Python 2 中的 xrange 对象) 是 lazy 的, 但 range 对象却不是迭代器
是的, 这让人很困惑
当谈论 Python 中的迭代器 (iterator) 和可迭代对象 (iterable) 时, 你很可能会听到有人重复 range 是迭代器的误解我认为这是非常严重误解, 如果你认为 range 对象是迭代器, 那么你关于迭代器是如何运行的心智模型还不够清楚从某种意义上来说, range 和迭代器都是惰性的, 但它们是以相当不同的方式实现惰性的
什么是迭代器(iterator)
在 Python 中, 可迭代对象就是你可以迭代的任何东西, 而迭代器就是实际迭代的东西
Iter-ables are able to be iterated over. Iter-ators are the agents that perform the iteration.
可以使用 iter 函数从任何可迭代对象中获取迭代器:
一旦有了迭代器, 可以用它做的唯一的事情就是获得它的下一个元素:
如果没有更多的元素了, 则会抛出一个 stop iteration exception:
所有的迭代器都是可迭代对象, 意思是你可以从一个迭代器中得到一个迭代器, 因此你可以遍历一个迭代器:
应该指出的是迭代器是有状态的, 在循环遍历一次迭代器后, 如果尝试再次循环, 它将为空:
在 Python 3 中,
enumeratezipreversed
和其他一些内置函数会返回迭代器:
生成器 (无论来自生成器函数还是生成器表达式) 是一种创建迭代器的简单方法:
我经常说迭代器是惰性的一次性可迭代对象 惰性是因为他们只循环计算项目, 单次使用是因为一旦从一个迭代器中消费了一个元素之后, 这个元素就永远消失了
什么是 range
Python 3 中的 range 对象 (Python 2 中的 xrange) 可以像任何其他可迭代对象一样循环使用:
因为 range 是可迭代对象, 所以可以从中得到一个迭代器:
但 range 对象本身不是迭代器, 我们不能在 range 对象上调用 next:
与迭代器不同的是, 我们可以遍历一个 range 对象而不消耗它:
如果我们使用迭代器完成此操作, 则第二次循环时不会得到任何元素:
宗上, 与 zip, enumerate, or generator 对象不同, range 对象不是迭代器
那么, 究竟 range 是什么
range 对象在某种意义上是惰性的, 因为它不会生成创建时包含的每个数字, 相反, 当我们在循环中需要的时候, 它才将这些数字返回给我们
下面是一个 range 对象和一个生成器(是一种迭代器):
不像生成器, range 对象有长度:
并且可以被索引:
与迭代器不同, 你可以询问他们是否包含某元素而不改变他们的状态:
如果你想要一个 range 对象的描述, 可以称它们为懒序列, range 是序列(如列表, 元组和字符串), 但并不包含任何内存中的内容, 而是通过计算来回答问题
为什么这个区别很重要
如果我告诉你某个对象是一个迭代器, 你会知道当在这个对象上调用 iter 函数时, 总会得到相同的的对象(按照定义):
确信可以在这个对象上调用 next 函数, 因为可以在所有的迭代器上调用 next 函数:
而且你会知道, 当遍历它时, 这些元素将从迭代器中被消耗掉, 有时候这个特性可以派上用场(以特殊的方式处理迭代器):
所以虽然看起来惰性可迭代对象和迭代器之间的区别很微妙, 但这些术语确实意味着不同的东西 虽然惰性可迭代对象是一个没有具体含义的非常普遍的术语, 但迭代器这个词意味着一个具有非常特定行为的对象
总结
如果你知道你可以循环遍历某个对象, 这是一个可迭代对象(iterable)
如果你知道你正在循环遍历的对象是在循环的时候计算出来, 那么这是一个惰性可迭代对象(lazy iterable)
如果你知道你可以传递一些东西给 next 函数, 它就是一个迭代器(这是最常见的惰性可迭代对象)
如果你可以循环多次而不用耗尽它, 它不是一个迭代器如果你不能将某些东西传递给 next 函数, 那么它不是一个迭代器 Python 3 的 range 对象不是迭代器 如果你正在指导别人关于 range 对象的知识, 请不要使用迭代器一词, 这会让人十分困惑, 并可能导致他人开始滥用迭代器这个词
来源: https://juejin.im/entry/5a991adf6fb9a028de442748