今天看到一篇很有意思的文章,说Pyhton3的range
是返回的什么?
很多人都会不假思索的说,这还不简单,在Python2中range()
会返回list
,到了Python3range
已经使用xrange
替换,返回的是一个迭代器(Iterator)。
恭喜你,答错了。
range()
返回的是一个Iterable,并不是一个Iterator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
a Python 3.6.3 (default, Nov 3 2017, 14:41:25) Type 'copyright', 'credits' or 'license' for more information IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. In [1]: a = range(10) In [2]: a Out[2]: range(0, 10) In [3]: import collections In [4]: isinstance(a, collections.Iterable) Out[4]: True In [5]: isinstance(a, collections.Iterator) Out[5]: False |
原理很简单,先简单说一下Iterable和Iterator,不要试图比较二者有什么不同,因为二者根本就是不同的概念。二者字面意思都非常明确:Iterable就是一个可迭代的对象,对其调用iter(Iterable)
将会得到一个迭代器;而Iterator就是一个迭代器,对其调用next(Iterator)
将会得到下一个元素。
Python推崇协议,说白了就是鸭子类型。你如果实现了__iter__()
,(即对你调用iter()
可以得到一个Iterator)那你就是一个Iterable;如果实现了__next__()
和__iter__()
就是一个Iterator。
等等,Iterator不就是调用next()
得到下一个元素就可以了?为什么Iterator也要实现Iterable的__iter__()
方法,这不纯粹啊!
为什么Python的Iterator要实现__iter__()
呢(通常的实现都是return self
)。官方文档中说的相当清楚。
Iterators are required to have an
__iter__()
method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.
简单翻译一下,就是说Iterator也要求实现__iter__()
,因为很多地方接收的参数是一个Iterable,如果所有的Iterator都是Iterable,那么这些用Iterable地方都可以无障碍地使用Iterator了。比如说for循环吧,关于for循环,我在Python的wiki中(已经比较老了)找到这样的描述:
Basically, any object with an iterable method can be used in a for loop. Even strings, despite not having an iterable method – but we’ll not get on to that here.
即,for循环拿到一个Iterable的Iterator,然后使用这个Iterator进行迭代。如果Iterator实现了__iter__()
方法,那么for循环就可以无障碍地对Iterator进行迭代了,Neat!想象一下,Python的生成器也是Iterator,如果for循环不能支持对Iterator迭代,生不如死啊。
所以对Iterator就有了这样一个“过分”的要求。我们可以认为,所有的Iterator都是Iterable。那么回到最初的问题,为什么range()
反回的是一个Iterable而不是Iterator呢?
Iterator是有状态的,只能遍历一次,是“消费型”的,不可以“二次消费”。Iterable是没有状态的(这里不太严谨,这句话暂且不提是Iterator的Iterable),每一次对Iterable调用iter()
都会得到一个新的迭代器。
考虑我们平时使用range()
,我们认为这是一个表示范围的一个容器。可以使用这个容器去初始化成别的容器,这没有任何问题。
1 2 3 4 5 |
>>> numbers = range(3) >>> tuple(numbers) (0, 1, 2) >>> tuple(numbers) (0, 1, 2) |
倘若range()
返回的是迭代器,那么上面这个看起来在正常不过的代码就有麻烦了:
1 2 3 4 5 |
>>> numbers = iter(range(3)) >>> tuple(numbers) (0, 1, 2) >>> tuple(numbers) () |
读到这里,你应该完全理解Iterable和Iterator了,再说点题外话。
有人可能觉得(我之前也这么觉得),这样的实现太不纯粹了,要是Iterator就是Iterator,Iterable就是Iterable不好吗?规定接口只接受Iterator,如果有Iterable的话必须手动获得一个新的Iterator然后使用。可以是可以,但是想象一下每次都初始化一个新的Iterator多费劲啊。并且肯定有地方会定义一个临时变量,万一忍不住忘记这已经迭代完了,被迭代第二次呢?
对Iterator进行迭代,是很符合人类思维的事情,因为迭代器本来就是可以迭代的。
对Iterable进行迭代,也是很符合人类思维的事情,”可迭代的对象“本来也是可迭代的,每次迭代的时候在内部初始化一个Iterator进行迭代。
所以现在想想,其实Iterator的这种行为就非常合理了。
Python是一门讲求实用主义的语言。随着越来越多地接触和使用这一门语言,我也越来越体会到这句话。另一个例子是Pyhton的元祖,这个数据结构的灵感来自ABC语言的compounds
。compounds
可以作为字典(在ABC中字典叫table)的合成键,也支持平行赋值。仅此而已。如果你想使用它,要么使用平行赋值取出全部的元素,要么作为一个整体来使用。这样compounds
的作用变得非常纯粹——一个没有字段名字的记录的集合。而Python的tuple,却支持了迭代、下标、切片,给人的印象更像是frozenlist!概念上已经失去了compounds
的纯粹。
但是这种实用主义的哲学让Python比ABC更灵活,更好用也更成功。
Although practicality beats purity.
Python 之禅如是说。
6⃣️6⃣️6⃣️
我,答对了!
恭喜 :)