当前位置: 首页 > Linux

为什么range不是迭代器?范围的类型是什么?

时间:2023-04-06 11:53:44 Linux

迭代器是最常用的(其中之一)23种设计模式,在Python中随处可见,我们经常使用它,但我们不一定意识到它的存在。在我的迭代器系列中(链接见文末),我提到了至少23种生成迭代器的方法。有的方法是专门用来生成迭代器的,有的方法是“偷偷”用迭代器来解决其他问题的。在系统学习迭代器之前,我一直以为range()方法也是用来生成迭代器的,但是现在突然发现它只生成可迭代对象,而不是迭代器!(PS:Python2中的range()生成一个列表,本文基于Python3,生成一个可迭代对象。)那么,我有这样一个问题:为什么range()不生成一个迭代器?在寻找答案的过程中,我发现自己对范围类型有一些误解。因此,本文将使您全面了解range,期待与您一起学习进步。1.什么是range()?它的语法:range(start,stop[,step]);start指计数起始值,默认为0;停止是指计数结束值,但不包括停止;step为步长,默认为1,不能为0。range()方法生成一个左闭右开的整数区间。>>>a=range(5)#即range(0,5)>>>arange(0,5)>>>len(a)5>>>forxina:>>>print(x,end="")01234对于range()函数,有几点需要注意:(1)表示一个左闭右开区间;(2)它接收的参数必须是整数,可以是负数,但不能是浮点数等其他类型;(3)是不可变序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素;(4)是可迭代对象,但不是迭代器。#(1)左闭右开>>>foriinrange(3,6):>>>print(i,end="")345#(2)参数类型>>>foriinrange(-8,-2,2):>>>打印(i,end="")-8-6-4>>>>范围(2.2)----------------------------TypeErrorTraceback(mostrecentcalllast)...TypeError:'float'objectcannotbeinterpretedasaninteger#(3)序列操作>>>b=range(1,10)>>>b[0]1>>>b[:-3]range(1,7)>>>b[0]=2TypeErrorTraceback(最近一次调用)...TypeError:'range'对象不支持项目分配#(4)不是迭代器>>>hasattr(range(3),'__iter__')True>>>hasattr(range(3),'__next__')False>>>hasattr(iter(range(3)),'__next__')True2。为什么range()不生成迭代器?可以获取迭代器的内置方法有很多,比如zip()、enumerate()、map()、filter()、reversed()等,但是像range()这样的少数方法只能获取可迭代对象(如有反例,欢迎告知)。这就是我存在知识误区的地方。在for循环中遍历时,可迭代对象与迭代器具有相同的性能,即惰性求值,空间复杂度和时间复杂度没有区别。我把两者的区别总结为“一二不同”:相同的是可以惰性迭代,不同的是可迭代对象不支持自遍历(即next()方法),并且迭代器本身不支持切片(即__getitem__()方法)。尽管存在这些差异,但很难断定它们中的哪一个更优越。现在微妙的是,为什么迭代器是为五个内置方法设计的,而可迭代对象是为range()方法设计的?将它们全部统一起来不是更好吗?事实上,为了标准化,Pyhton已经做了很多这种事情。比如Python2中有range()和xrange()两个方法,Python3干掉其中一个,用的是“李代桃僵尸”的方法。为什么不更正式一点,让range()生成一个迭代器呢?关于这个问题,我没有找到官方的解释,以下纯属个人看法。zip()等方法需要接收某些可迭代对象的参数,这是一个对它们进行再处理的过程,所以他们也希望立即产生某些结果,所以Python开发者将这个结果设计成一个迭代器。这样还有一个好处,当用作参数的可迭代对象发生变化时,生成的迭代器不会因为被消耗而被错误使用。range()方法不同。它接收的参数不是可迭代对象。它是一个初始处理的过程,所以设计成一个可迭代的对象,可以直接使用,也可以用于其他再处理目的。比如zip()等方法,完全可以接受range类型的参数。>>>用于zip(范围(1,6,2),范围(2,7,2)):>>>打印(i,end="")(1,2)(3,4)(5,6)也就是说range()方法是一个初级生产者,它生产的原料有很大的用处。如果早早的变成迭代器,无疑是多此一举。你觉得这样的解读有道理吗?欢迎与我讨论这个话题。3.什么是范围类型?以上是我对“为什么range()不生成迭代器”的回答。顺着这个思路,研究了一下它生成的range对象,发现这个range对象并不简单。第一个奇怪的是,它竟然是一个不可变的序列!我从来没有注意过这个。尽管我从未想过修改range()的值,但这个不可修改的特性让我感到惊讶。看文档,官方明确是这样划分的——基本的序列类型分为三种:list、tuple和range(范围)对象。(基本序列类型有lists、tuples、rangeobjects三种。)这个我倒是没注意过,但是range类型其实是基本序列,和lists、tuples地位一样!我一直记得字符串是不可变的序列类型。没想到这里还有另外一种不可变的序列类型。范围序列和其他序列类型有什么区别?常用序列支持的操作有12种,在《你真的知道Python的字符串是什么吗?》一文中有提到。range序列只支持其中的10个,不支持加法拼接和乘法重复。>>>范围(2)+范围(3)---------------------------------------TypeErrorTraceback(最近一次调用)...TypeError:+不支持的操作数类型:'range'和'range'>>>range(2)*2--------------------------------------TypeErrorTraceback(最近一次调用)...TypeError:不支持的操作数类型(s)for*:'range'and'int'那么问题来了:同样是不可变序列,为什么字符串和元组支持以上两种操作,而范围序列不支持呢?不可变序列虽然不能直接修改,但是我们可以将其复制到一个新的序列中进行操作。为什么范围对象甚至不支持这个?我们看看官方文档的解释:...由于范围对象只能表示遵循严格模式的序列,重复和连接通常会违反该模式。原因是range对象只表示一个遵循严格模式的序列,而重复和拼接通常会破坏这种模式……问题的关键在于range序列的模式。仔细想想,其实它代表的是一个等差数列(喵呜,高中数学知识还没忘。。。),拼接两个等差级数,或者反复拼接一个等差级数,想想确实不对,这就是为什么range类型不支持这两种操作。由此可知,其他的修改动作也会破坏等差数列的结构,根本不修改就可以了。4.小结回顾全文,得到了两个冷门的结论:range是一个可迭代对象而不是迭代器;范围对象是一个不可变的算术序列。如果只看结论,可能感觉不到,说没什么大不了的。但如果我问,为什么range不是迭代器,为什么range是不可变序列?对于这两个问题,你还能回答一个自圆其说的设计思路吗?(PS:我已经决定了,如果有机会采访别人,我一定要问这两个问题~)因为range对象微妙而有趣的特性,我觉得这篇文章值得写。这篇文章是作为迭代器的系列文章来写的,所以对迭代器的基础知识介绍不多。欢迎阅读往期文章。另外,还有一个特别值得单独写的迭代器,那就是生成器,敬请期待后续推文~猜你想看:进阶Python:迭代器与迭代器切片进阶Python:设计模式迭代器模式你真的知道什么是Python字符串吗?官方文档:http://t.cn/EGMzJt8--------------------本文原发于微信公众号【蟒猫】,后台回复“爱学习”,免费获取20多本精选电子书。