range()有哪些?为何不生产迭代器?
迭代器是 23 种设计模式中最常用的一种(之一),在 Python 中随处可见它的身影,我们时常用到它,但是却纷歧定意识到它的存在。在对于迭代器的系列文章中(链会见文末),我至少提到了 23 种生成迭代器的办法。有些办法是专门用于生成迭代器的,还有一些办法则是为理解决另外题目而“黑暗”运用到迭代器。
在系统学习迭代器以前,我不断认为 range() 办法也是用于生成迭代器的,此刻却忽然发明,它生成的只是可迭代对象,而并不是迭代器! (PS:Python2 中 range() 生成的是列表,本文基于Python3,生成的是可迭代对象)
于是,我有了这样的疑难:为何 range() 不生成迭代器呢?在查寻答案的历程中,我发明本人对 range 类型的相识存在一些误区。因而,本文将和大家全面地相识一下 range ,等待与你共同窗习进步。
1、range() 有哪些?
它的语法:range(start, stop [,step]) ;start 指的是计数起始值,默许是 0;stop 指的是计数完毕值,但不包含 stop ;step 是步长,默许为 1,不成认为 0 。range() 办法生成一段左闭右开的整数范畴。
>>> a = range(5) # 即 range(0,5) >>> a range(0, 5) >>> len(a) 5 >>> for x in a: >>> print(x,end=" ") 0 1 2 3 4
关于 range() 函数,有几个注意点:(1)它表示的是左闭右开区间;(2)它接收的参数必需是整数,可以是负数,但不克不及是浮点数等其它类型;(3)它是不成变的序列类型,可以进行推断元素、查寻元素、切片等操纵,但不克不及修改元素;(4)它是可迭代对象,却不是迭代器。
# (1)左闭右开 >>> for i in range(3, 6): >>> print(i,end=" ") 3 4 5 # (2)参数类型 >>> for i in range(-8, -2, 2): >>> print(i,end=" ") -8 -6 -4 >>> range(2.2) ---------------------------- TypeError Traceback (most recent call last) ... TypeError: 'float' object cannot be interpreted as an integer # (3)序列操纵 >>> b = range(1,10) >>> b[0] 1 >>> b[:-3] range(1, 7) >>> b[0] = 2 TypeError Traceback (most recent call last) ... TypeError: 'range' object does not support item assignment # (4)不是迭代器 >>> hasattr(range(3),'__iter__') True >>> hasattr(range(3),'__next__') False >>> hasattr(iter(range(3)),'__next__') True
2、 为何range()不生产迭代器?
可以获得迭代器的内置办法许多,例如 zip() 、enumerate()、map()、filter() 和 reversed() 等等,但是像 range() 这样仅仅得到的是可迭代对象的办法就司空见惯了(如有反例,欢送奉告)。这就是我存在见识误区的地方。
在 for-轮回 遍历时,可迭代对象与迭代器的机能是同样的,即它们都是惰性求值的,在空间复杂度与工夫复杂度上并无悬殊。我曾具体过两者的差别是“一同两不一样”:雷同的是都可惰性迭代,不一样的是可迭代对象不支撑自遍历(即next()办法),而迭代器自身不支撑切片(即__getitem__()
办法)。
虽然有这些差别,但很难得出结论说它们哪个更优。此刻奥妙之处就在于,为何给 5 种内置办法都设计了迭代器,偏偏给 range() 办法设计的就是可迭代对象呢?把它们都同一起来,不是更好么?
事实上,Pyhton 为了标准性就干过不少这种事,例如,Python2 中有 range() 和 xrange() 两种办法,而 Python3 就干掉了其中一种,还用了“李代桃僵”法。为何不更标准点,令 range() 生成的是迭代器呢?
对于这个题目,我没寻到官方解释,下列纯属个人观念 。
zip() 等办法都需要接收肯定的可迭代对象的参数,是对它们的一种再加工的历程,因而也但愿即将产出肯定的效果来,所以 Python 开发者就设计了这个效果是迭代器。这样还有一个益处,即当作为参数的可迭代对象产生变化的时候,作为效果的迭代器由于是耗损型的,不会被差错地运用。
而 range() 办法就不一样了,它接收的参数不是可迭代对象,自身是一种初次加工的历程,所以设计它为可迭代对象,既可以直接运用,也可以用于其它再加工用法。例如,zip() 等办法就完全可以接收 range 类型的参数。
>>> for i in zip(range(1,6,2), range(2,7,2)): >>> print(i, end="") (1, 2)(3, 4)(5, 6)
也就是说,range() 办法作为一种低级生产者,它生产的原料自身就有很大用法,早早把它变为迭代器的话,无疑是一种多此一举的行为。
关于这种解读,你可否觉得有原理呢?欢送就这个话题与我探究。
3、range 类型有哪些?
以上是我对“为何range()不发生迭代器”的一种解答。顺着这个思绪,我研究了一下它发生的 range 对象,一研究就发明,这个 range 对象也并不简略。
第一蹊跷怪僻的一点就是,它居然是不成变序列!我从未注意过这一点。虽然说,我从未想过修改 range() 的值,但这一不成修改的特性还是令我惊叹。
翻看文档,官方是这样明白划分的——有三种根本的序列类型:列表、元组和范畴(range)对象。(There are three basic sequence types: lists, tuples, and range objects.)
这我倒不断没注意,本来 range 类型竟然跟列表和元组是同样地位的根基序列!我不断记挂着字符串是不成变的序列类型,未曾想,这里还有一名不成变的序列类型呢。
那 range 序列跟其它序列类型有什么悬殊呢?
普通序列都支撑的操纵有 12 种,在《你真的晓得Python的字符串有哪些吗?》这篇文章里提到过。range 序列只支撑其中的 10 种,不支撑进行加法拼接与乘法反复。
>>> range(2) + range(3) ----------------------------------------- TypeError Traceback (most recent call last) ... TypeError: unsupported operand type(s) for +: 'range' and 'range' >>> range(2)*2 ----------------------------------------- TypeError Traceback (most recent call last) ... TypeError: unsupported operand type(s) for *: 'range' and 'int'
那么题目来了:一样是不成变序列,为何字符串和元组就支撑上述两种操纵,而偏偏 range 序列不支撑呢?虽然不克不及直接修改不成变序列,但我们可以将它们拷贝到新的序列上进行操纵啊,为什么 range 对象连这都不支撑呢?
且看官方文档的解释:
...due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern.缘由是 range 对象仅仅表示一个遵循着严厉模式的序列,而反复与拼接平常会毁坏这种模式...
题目的关键就在于 range 序列的 pattern,细心想想,其实它表示的就是一个等差数列啊(喵,高中数学见识没忘...),拼接两个等差数列,或者反复拼接一个等差数列,想想的确不当,这就是为啥 range 类型不支撑这两个操纵的缘由了。由此推论,其它修改行动也会毁坏等差数列构造,所以通通不给修改就是了。
4、小结
回忆全文,我得到了两个偏冷门的结论:range 是可迭代对象而不是迭代器;range 对象是不成变的等差序列。
若纯正看结论的话,你或许没有感到,也许还会说这没啥了不起啊。但要是我追问,为何 range 不是迭代器呢,为何 range 是不成变序列呢?对这俩题目,你可否还能答出个无懈可击的设计思想呢?(PS:我决议了,如有时机面试他人,我须要问这两个题目的嘿~)
因为 range 对象这细微而成心思的特性,我觉得这篇文章写得值了。本文是作为迭代器系列文章的一篇来写的,所以关于迭代器的根基见识介绍未几,欢送查看以前的文章。别的,还有一种特别的迭代器也值得独自成文,那就是生成器了,敬请等待后续推文哦~
以上就是range()有哪些?为何不生产迭代器?的细致内容,更多请关注 百分百源码网 其它相干文章!