为什么范围(开始,结束)不包括结束?


305
>>> range(1,11)

给你

[1,2,3,4,5,6,7,8,9,10]

为什么不选择1-11?

他们是只是决定随机地这样做,还是有一些我没有看到的价值?


11
读Dijkstra算法,ewd831
SilentGhost

11
基本上,您是为另一组错误选择一组错误的错误。一组更有可能导致循环提前终止,而另一组则可能导致异常(或其他语言的缓冲区溢出)。一旦编写了一堆代码,您就会发现行为的选择range()更加有意义
John La Rooy 2010年

32

35
@unutbu Djikstra文章经常在该主题中被引用,但在此没有提供任何价值,人们将其用作对权威的诉求。他针对OP的问题给出的唯一相关的伪原因是,他碰巧感觉到,在空序列中的特定情况下,包含上限变得“不自然”和“丑陋”,这一个完全主观的立场,很容易被反对,因此它并没有带来太多好处。Mesa的“实验”在不了解其特定约束或评估方法的情况下并没有多大价值。
sundar-恢复莫妮卡

6
@andreasdr但是,即使装饰性参数有效,Python的方法也不会引入新的可读性问题吗?在共使用英语术语“范围”意味着什么范围东西的东西-比如一个区间。len(list(range(1,2,)))返回1,len(list(2,(2)))返回2,这是您真正必须学习的东西。
阿明2016年

Answers:


245

因为调用range(0, 10)return [0,1,2,3,4,5,6,7,8,9]包含10个等于equals的元素更为常见len(range(0, 10))。请记住,程序员更喜欢基于0的索引。

另外,请考虑以下常见代码段:

for i in range(len(li)):
    pass

您能看到如果range()精确len(li)到这将是有问题的吗?程序员需要显式减1。这也遵循程序员喜欢的共同趋势for(int i = 0; i < 10; i++)for(int i = 0; i <= 9; i++)

如果您经常以1开头的范围调用range,则可能需要定义自己的函数:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

48
如果那是推理的话,参数不是range(start, count)吗?
马克·兰瑟姆

3
@shogun起始值默认为0,即range(10)等于range(0, 10)
moinudin

4
range1将无法使用步长不同于的范围1
dimo414

6
您解释了range(x)应该以0开头,x将是“范围的长度”。好。但是您没有解释为什么range(x,y)应该以x开头并以y-1结尾。如果程序员想要一个i介于1到3之间的for循环,则他必须显式加1。这是否真的很方便?
阿明2016年

7
for i in range(len(li)):是一种反模式。一个应该使用enumerate
汉斯

27

尽管这里有一些有用的算法解释,但我认为添加一些简单的“现实生活”推理可能会有助于以这种方式工作,这对我向年轻新手介绍该主题很有用:

对于“ range(1,10)”之类的东西,可能会因认为一对参数代表“开始和结束”而产生混淆。

它实际上是开始和“停止”。

现在,如果它 “结束”值,是的,您可能希望该数字将作为序列中的最后一个条目包括在内。但这不是“终点”。

其他人错误地将该参数称为“ count”,因为如果您只使用“ range(n)”,那么它当然会迭代“ n”次。添加开始参数时,此逻辑将失效。

因此,关键是要记住它的名称:“ stop ”。这意味着到达该点时,迭代将立即停止。不经过这一点。

因此,虽然“开始”确实代表要包含的第一个值,但在达到“停止”值时,它“中断”而不是在停止之前继续处理“该一个”。

我用来向孩子们解释的一个比喻是,具有讽刺意味的是,它比孩子们表现得更好!它不会应有的状态下停止-它会在未完成操作的情况下立即停止。(他们得到这个;)

另一个比喻-当您开车时,您不会通过停车/屈服/“放弃”标志,而最终会停在您的汽车旁边或后面。从技术上讲,您停止时仍未达到目标。它不包含在“您在旅途中传递的事物”中。

我希望其中一些有助于解释Pythonitos / Pythonitas!


这种解释更加直观。谢谢
弗雷德(Fred)

孩子们的解释真是太好笑了!
安东尼·哈奇金斯19/12/12

1
您正在尝试将口红涂在猪身上。“停止”和“结束”之间的区别是荒谬的。如果我从1变到7,那么我还没有通过7。这是Python的一个缺陷,因为它有不同的开始和结束位置约定。在包括人类的其他语言中,“从X到Y”表示“从X到Y”。在Python中,“ X:Y”表示“ X:Y-1”。如果您的开会时间是9点到11点,您是否会告诉人们这是9点到12点还是8点到11点?
bzip2

24

互斥范围确实有一些好处:

一方面,每一项range(0,n)都是长度列表的有效索引n

range(0,n)具有的长度n,不是n+1包含范围的长度。


18

与基于零的索引和结合使用时效果很好len()。例如,如果列表中有10个项目x,则它们的编号为0-9。range(len(x))给你0-9。

当然,人们会告诉你这是更Python做for item in xfor index, item in enumerate(x)不是for i in range(len(x))

切片也是如此:foo[1:4]是项的1-3 foo(请记住,由于从零开始的索引,项1实际上是第二项)。为了保持一致,它们都应以相同的方式工作。

我认为它是:“您想要的第一个数字,然后是您不需要的第一个数字。” 如果您想要1-10,则您不希望的第一个数字是11,所以它是range(1, 11)

如果它在特定的应用程序中变得繁琐,那么编写一个简单的辅助函数就很容易了,该函数将1加到结束索引并调用range()


1
同意切片。 w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
kevpie 2010年

def full_range(start,stop): return range(start,stop+1) ## helper function
nobar 2011年

也许应该列举枚举的示例for index, item in enumerate(x)以避免混淆
肖恩肖恩

@seans谢谢,已修复。
kindall

12

这对于分割范围也很有用;range(a,b)可以分为range(a, x)range(x, b),而在包含范围内,您可以编写x-1x+1。尽管您几乎不需要拆分范围,但您确实倾向于拆分列表,这是对列表进行切片的原因之一,其中l[a:b]包括第a个元素,但不包括第b个元素。然后range具有相同的属性使其完全一致。


11

范围的长度是上限值减去下限值。

它非常类似于以下内容:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

用C风格的语言。

也像Ruby的范围:

1...11 #this is a range from 1 to 10

但是,Ruby认识到很多时候您都想包含终端值,并提供了另一种语法:

1..10 #this is also a range from 1 to 10

17
加!我不使用Ruby,但我可以想像,1..10VS 1...10是很难读代码的时候区分!
moinudin

6
@marcog-当您知道这两种形式存在时,您的眼睛会
注意到

11
Ruby的范围运算符非常直观。格式越长,序列越短。咳嗽
Russell Borogove 2010年

4
@Russell,也许1 ............ 20的范围应与1..10相同。现在,这将是一些值得转换的语法糖。;)
kevpie 2010年

4
@Russell多余的点将最后一个项目挤出范围:)
Skilldrick 2010年

5

基本上在python中range(n)迭代n时间,这是排他性,这就是为什么它在打印时不给出最后一个值的原因,我们可以创建一个给出包容性值的函数,这意味着它也将打印范围中提到的最后一个值。

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()

4

考虑代码

for i in range(10):
    print "You'll see this 10 times", i

想法是获得一个length的列表y-x,您可以(如上所述)进行迭代。

阅读有关范围的python文档 -他们将for循环迭代视为主要用例。


1

在许多情况下,这样做更方便。

基本上,我们可以将范围视为start和之间的间隔end。如果是start <= end,则它们之间的间隔长度为end - start。如果len实际定义为长度,则将具有:

len(range(start, end)) == start - end

但是,我们计算范围内包含的整数,而不是测量间隔的长度。为了保持上述属性为真,我们应包括一个端点,而排除另一个端点。

添加step参数就像引入长度单位一样。在这种情况下,您会期望

len(range(start, end, step)) == (start - end) / step

对于长度。要获得计数,您只需使用整数除法。


这些针对Python不一致的辩护非常有趣。如果我想要两个数字之间的间隔,为什么我要用减法来得到差而不是间隔?在开始位置和结束位置使用不同的索引约定是不一致的。为什么需要写“ 5:22”才能获得第5至21位?
bzip2

它不是Python的,在整个领域都很普遍。在C,Java和Ruby的,你的名字
Arseny

我的意思是说索引是很常见的,而不是其他语言必然具有相同的确切对象类型
Arseny
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.