Python range 的相等规则:终点与步长不同,为什么仍然可能是同一个序列
两项批处理配置的终点分别是六和五,构造成range以后比较却返回True。若直接拿range当缓存键,第二项还会覆盖第一项。这并非终点被修改,而是range的相等关系比较实际表示的整数序列,不要求构造参数逐字相同。
本例在Linux、CPython 3.12.14实跑。保存为demo.py并运行python3 demo.py,所有范围都很短,只用内存字典观察键合并。程序同时检查普通多元素、单元素、空范围和不同序列类型,避免只凭一组巧合得出结论。
AI生成的概念示意图:不同长度的边框包围相同的几个落点,表示范围描述与实际产生的值可以分别比较;不是精确坐标图。
完整程序与本地结果
完整可运行程序
first = range(0, 6, 2)
second = range(0, 5, 2)
assert first == second
print("same values:", list(first), list(second))
print("different stops:", first.stop, second.stop)
print("ranges equal:", first == second)
single_a = range(7, 8, 1)
single_b = range(7, 100, 100)
assert list(single_a) == list(single_b) == [7]
assert single_a == single_b
print("singletons equal:", single_a == single_b)
empty_a = range(0)
empty_b = range(10, 20, -1)
assert empty_a == empty_b
print("empty ranges equal:", empty_a == empty_b)
def specification(value):
return value.start, value.stop, value.step
assert specification(first) != specification(second)
print("same specification:", specification(first) == specification(second))
assert first != [0, 2, 4]
print("range equals list:", first == [0, 2, 4])
cache = {first: "first job", second: "second job"}
assert len(cache) == 1
print("range-key entries:", len(cache), "value:", cache[first])
spec_cache = {specification(first): "first job", specification(second): "second job"}
assert len(spec_cache) == 2
print("specification-key entries:", len(spec_cache))本次实际输出(以下为结果,不是程序)
same values: [0, 2, 4] [0, 2, 4] different stops: 6 5 ranges equal: True singletons equal: True empty ranges equal: True same specification: False range equals list: False range-key entries: 1 value: second job specification-key entries: 2
stop不同,不代表最后一项不同
first是从零开始、终点六、步长二的范围;second把终点改成五。两者实际都生成零、二、四,因为stop不包含在结果中。输出同时展示两个列表、原来的stop属性和相等结果,让“参数不同但序列相同”成为可以直接核对的事实。
如果任务只关心依次处理哪些编号,两者相等是合理的。但如果终点记录用户设置的上限,即使这次恰好没有触及上限,两个配置仍可能需要分别保存。先确定是在比较处理结果,还是比较配置意图,才能决定应使用哪一种键。
只有一个值时,步长也可能不同
single_a与single_b都只产生七,前者步长一,后者步长一百。由于后面没有第二项,步长差异没有体现在序列里,所以它们仍然相等。这提醒我们不能只看到step不同,就推断两次运行一定会得到不同编号。
不过这也不表示修改步长永远无影响。把终点进一步扩大,第二项开始出现时,两个序列就可能分开。比较当前值序列与预测未来修改后的行为是两件事;如果要保留可编辑配置,应保存参数本身,而不是把当前相等关系当作长期等价承诺。
空范围可以有完全不同的描述
empty_a是通常写法的空范围,empty_b从十开始、终点二十、步长负一,方向使它同样不能产生任何值。相等比较返回True,反映的只是两边都为空。一个空范围并不保存“为什么为空”这种业务解释。
若空结果意味着错误配置,仍需检查方向与上下界,给出合适提示;若业务允许空任务,就按正常空任务处理。不能指望range对象替你区分“用户明确选择零项”和“起点、终点写反”,这些原因要从原始请求及校验规则中判断。
缓存键会采用对象自己的相等规则
cache依次以first和second作为键写入任务名,最终只有一个条目,取first得到后写入的second job。两个相等的可哈希对象对应同一个字典键位置;这里不是缓存自动分析了任务内容,而是键已经把这两种范围表示视为相等。
如果缓存目标是“这些编号的处理结果”,这种合并可能正合适。若缓存还依赖原始上限、分页说明或用户选择,就应把那些信息一起纳入键。缺少真正影响结果的条件,会让缓存错误复用;加入与结果无关的展示差异,则可能只会降低命中率。
明确比较参数,才能保留描述差异
specification取出start、stop、step组成三元组。两个范围的三元组不相等,以它们为键的字典因此保留两个条目。这个函数没有展开范围,适合表达规范化后的参数身份,也让键的含义在代码里更直接。
它仍不会保留原始调用写法,例如省略默认参数与显式传入默认参数。若需要审计用户原文,还应独立保存原始配置。参数三元组表达的是range对象公开的三个属性,不是完整的用户输入历史。
最后,first与内容相同的列表比较为False。本例的序列式相等规则针对range之间,不能推广成所有不同容器只要元素一致就必定相等。小样本展开为列表是为了展示值;正式比较两个range无需先构造巨大列表,避免为比较而无谓分配内存。
参考资料
资料核验日期:2026年10月2日。以上输出来自固定输入的本地实跑,退出码为0。


