跳至主要内容
Midsummer's Blog
返回

深度解析:Python 中的 range 为什么设计成“左闭右开”?

编辑页面

刚接触 Python 的初学者几乎都会产生同一个困惑:

for i in range(1, 8):
    print(i) # 输出的是 1 到 7,居然不包含 8!

每次写循环或切片,都要在心里琢磨到底要不要 +1 或者 -1,让人感觉反直觉又容易出错。

但这绝不是 Python 设计师的随性之举,而是计算机科学界历经数十年演进后沉淀出的最优设计公理。


一、 根源:1982 年 Dijkstra 的经典数学推导

关于区间边界的讨论,最严谨的答案早在 1982 年就被图灵奖得主 艾兹格·迪杰斯特拉(Edsger W. Dijkstra)在一篇著名的手稿《Why numbering should start at zero》(为什么编号应从零开始)中彻底终结了。

如果要表示一个从 22 到 1212 的自然数连续序列,理论上只有四种区间写法:

方案形式示例
a(左闭右开)a≤i<ba \le i < b2≤i<132 \le i < 13
b(左开右闭)a<i≤ba < i \le b1<i≤121 < i \le 12
c(双闭区间)a≤i≤ba \le i \le b2≤i≤122 \le i \le 12
d(双开区间)a<i<ba < i < b1<i<131 < i < 13

Dijkstra 用了三轮无可辩驳的淘汰逻辑,一步步排除了其他三种形式:

1. 第一轮筛选:计算区间长度(a、b 胜出)

2. 第二轮筛选:自然数下界的选取(a、c 胜出)

3. 第三轮筛选:空区间的表示(a 最终胜出)

这是最致命的一点:

结论:左闭右开 [start,stop)[start, stop) 在数学与计算机科学语义上以压倒性优势胜出。


二、 落地工程:日常编码中的三大巨大优势

当把这个理论投射到日常的 Python 编程中时,左闭右开的优势体现得淋漓尽致:

1. 数组切片无缝拼合(最核心特性)

在列表和字符串切片中,左闭右开实现了前所未有的优雅:

data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
split_point = 5

left_part = data[:split_point]   # [0, 1, 2, 3, 4]
right_part = data[split_point:]  # [5, 6, 7, 8, 9]

# 核心等式永远成立:
assert left_part + right_part == data

前一段的终点直接就是后一段的起点!

2. 与 0 索引(0-based index)完美契合

现代编程语言普遍采用从 0 开始索引。对于一个长度为 NN 的数组:

3. 空区间的自然处理

list(range(5, 5))  # []
data[3:3]          # []

当起始位置等于结束位置时,天然获得空列表,无需额外的条件判断防护。


三、 一个有趣的特例:为什么 random.randint(1, 6) 包含 6?

细心的开发者可能会发现一个例外:

import random
random.randint(1, 6) # 这里居然是包含 6 的!

这其实是 Python 早期的一个设计历史遗留:


四、 工业界的跨语言共识

左闭右开绝不是 Python 的专属,而是现代主流系统与语言的一致选择:


五、 结语

下次当你写下 range(1, 8) 时,不必再觉得反直觉。这行看似简单的代码背后,凝聚了四十多年前计算机大师对逻辑纯粹性的严密推演,以及无数工程师用血泪 Bug 换来的最佳实践。


参考与来源


编辑页面
分享这篇文章: