☰
Python的索引与切片原来该这样理解
2026/10/10 5:31:28 网站建设 项目流程

前言


索引(indexing)和切片(slicing)是 Python 序列的两把基础钥匙,但很多人是"用得会、说不出"。一旦遇到边界情况——为什么s[100]报错而s[100:200]不报错?为什么s[::-1]能反转?s[-0]到底是什么——就只能靠试。其实它们背后有几条很一致的定义,理顺之后,边界情况不用记也能推出来。


要澄清一个流传很广的误解:"切片是索引的批量版"。这个说法不太准确。索引和切片是两套不同的规则:索引要求下标必须落在[0, len)之内,越界是错误;切片则先把下标规整到合法范围,再按"左闭右开"取一段,越界被容忍。正因为规则不同,才有"索引报错、切片不报错"这个看似矛盾的现象。


本文用一个统一的心智模型把两者串起来,配上一张下标对照图,最后归纳成可以现场推导的几条规则。代码基于 Python 3.8 及以上。Python 2.7 已于 2020 年 1 月 1 日停止维护,本文不使用 Python 2 的语法。


一、先建立一张下标图


拿s = "Python"举例,把每个字符、正下标和负下标画在一起看:


字符 P y t h o n
正下标 0 1 2 3 4 5
负下标 -6 -5 -4 -3 -2 -1

两条规律一眼可见:



  • 正下标从0开始,最大的合法下标是len(s) - 1,也就是5。

  • 负下标从-1开始,表示最后一个字符;最小的是-len(s),也就是-6。


负下标-k的值等于len(s) - k(当k在1..len范围内)。所以s[-1]和s[5]是同一个字符。


二、索引:必须落在合法范围


索引的规则只有一句:下标必须在[-len, len-1]内,否则抛IndexError。


# 适用于 Python 3.8+
s = "Python"
print(s[0]) # P
print(s[5]) # n
print(s[-1]) # n
print(s[-6]) # P
# print(s[6]) # IndexError: string index out of range
# print(s[-7]) # IndexError: string index out of range

注意负下标下界是-len,-6合法,-7就越界了。


关于-0,要特别提一句:-0就是0,它不是"从末尾数的第 0 个"。s[-0]等于s[0],取到第一个字符。很多人以为s[-0]会像s[-1]那样指向末尾,这是个典型误解。


三、切片:先规整边界,再左闭右开


切片的规则稍多,但都是确定的。对s[i:j]:



  • i省略或为None时取0;j省略或为None时取len(s)。

  • i或j为负时,加上len(s)。

  • 规整后,小于0的按0处理,大于len(s)的按len(s)处理。

  • 结果包含下标k,条件是i <= k < j。若i >= j,结果为空。


# 适用于 Python 3.8+
s = "Python"
print(s[1:4]) # yth 下标 1、2、3
print(s[-3:-1]) # ho 等价于 s[3:5]
print(s[1:100]) # ython 100 被夹成 6
print(s[-100:2]) # Py -100 被夹成 0
print(s[4:1]) # '' 起点 >= 终点

官方文档对这段规则的原文表述是:i或j小于-len(s)时用0,大于len(s)时用len(s);i大于等于j时切片为空。所以切片永远不会因为越界而报错,最多给你一个空串。


切片语法背后其实是一个内置对象slice。s[1:4]在求值时相当于构造slice(1, 4)再交给下标操作,两个参数时签名是slice(start, stop),三个参数是slice(start, stop, step):


# 适用于 Python 3.8+
s = "Python"
sl = slice(1, 4)
print(s[sl]) # yth,与 s[1:4] 等价
print(sl.start, sl.stop) # 1 4
print(sl.step) # None

需要把"一段范围"当作数据传来传去时,slice对象比分别传start、stop更整洁,比如自定义类实现__getitem__时就能直接收到它。


四、理解负数步长


带步长的切片s[i:j:k],取的是下标序列i, i+k, i+2k, ...,直到到达j(不含j)。关键是步长的正负会改变i、j的默认值:




步长i默认j默认



正(k > 0)0(从头)len(s)(到尾)

负(k < 0)len(s) - 1(从尾)"开头之前",即到下标 0 为止



# 适用于 Python 3.8+
s = "Python"
print(s[::2]) # Pto 下标 0、2、4
print(s[1::2]) # yhn 下标 1、3、5
print(s[::-1]) # nohtyP 整串反转
print(s[4:1:-1]) # oht 下标 4、3、2
print(s[1::-1]) # yP 下标 1、0

有了这张表,s[::-1]为什么能反转就清楚了:负步长让起点默认在末尾、终点默认在开头之前,于是从后往前逐个取。而s[4:1:-1]手动指定了i=4、j=1,取下标 4、3、2(到 1 停,不含 1)。


k不能为0,否则抛ValueError。


五、切片赋值:只对可变序列成立


对str、tuple这类不可变序列,切片只能读;对list这类可变序列,切片还能写。


# 适用于 Python 3.8+
lst = [0, 1, 2, 3, 4, 5]
lst[2:4] = ["a", "b", "c"] # 右侧个数不必等于左侧长度
print(lst) # [0, 1, 'a', 'b', 'c', 4, 5]

但str不行:


# 适用于 Python 3.8+
s = "Python"
# s[0:2] = "Ja" # TypeError: 'str' object does not support item assignment

原因还是那句:str不可变。想"改"字符串,只能拼出新串。


实战:用同一套规则解析定长字段


定长格式的记录(每段占固定字符数)是索引和切片的经典用武之地。


# 适用于 Python 3.8+
# 假设每行定长:姓名 6 字符、年龄 3 字符、部门 4 字符,共 13 字符
LINE = "Alice 025研发 "
# 0..5 6..8 9..12

def parse_fixed(line):
line = line.ljust(13) # 不足补齐,避免切片拿短
name = line[0:6].strip()
age = line[6:9].strip()
dept = line[9:13].strip()
return name, age, dept

print(parse_fixed(LINE)) # ('Alice', '025', '研发')

这里每个字段的起点是"上一个字段的终点",正因为切片左闭右开,[0:6]和[6:9]首尾相接、不重不漏。如果切片是"左闭右闭",每一段都得写成[起点:终点-1],很容易差一。


常见坑点



  1. 混淆索引与切片的容错性


❌ 以为s[len(s)]会像切片那样安静返回空


✅s[len(s)]抛IndexError;只有s[len(s):]这类切片才返回空串



  1. 把s[-0]当成末尾


❌s = "abc"; print(s[-0])期待c


✅-0就是0,输出a;要末尾用s[-1]



  1. 弄错左闭右开


❌"Python"[0:2]以为能取到Pyt


✅ 它只取下标 0、1,得到Py;要含下标 2 得写[0:3]



  1. 负数步长下起点终点想当然


❌s = "abcdef"; s[3:0:-1]以为能取全到开头,实际只得到下标 3、2、1


✅ 负步长要取到开头,终点要省略或写None:s[3::-1]得到dcba



  1. 用find结果当索引却不判 -1


❌i = s.find(":"); s[i + 1:],找不到时-1 + 1 = 0,返回整串


✅ 先判断i != -1,或改用partition并检查分隔符非空



  1. 拿切片去做深拷贝


❌ 嵌套列表b = a[:]后改b[0][0],a也跟着变


✅ 切片是浅拷贝;深拷贝要copy.deepcopy(a)



  1. 步长写 0


❌s[::0]→ValueError: slice step cannot be zero


✅ 步长不允许为 0;取全部就省略步长



  1. 对字符串用切片赋值


❌s[1:2] = "X"→TypeError


✅ 只有可变序列支持切片赋值;字符串要拼出新串


总结




问题规则



索引合法范围-len到len-1,越界抛IndexError

切片越界下标夹到[0, len],不报错

左闭右开含start,不含stop;start >= stop得空串

-0等于0,不是末尾

正步长默认从0到len(s)

负步长默认从len-1到"开头之前"

切片赋值只有可变序列支持,str不支持



索引和切片不是同一件事的两种写法,而是两套定位规则。索引像"精确取一件",要求坐标必须有效;切片像"划一段范围",坐标超出会被温和地收进边界。把"左闭右开"和"负步长改变默认方向"这两条吃透,s[::-1]反转、s[1:100]不报错、s[-0]取到首字符这些现象就都能当场推出来,而不是靠背例子。


最后提一句版本:本文的写法面向 Python 3。Python 2 里print是语句而不是函数(3 里必须写print(...)),xrange已移除(3 里range本身就是惰性的),unicode类型统一成了str,整数除法也从3 / 2 == 1变成了3 / 2 == 1.5(整除要写//)。Python 2.7 已于 2020 年 1 月 1 日停止维护,这些旧写法不要再用了。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询