Python 字符串切片与索引(精准提取字符的核心方法)
前言从字符串里「取出想要的那几个字符」是日常编码里出现频率最高的动作之一。Python 给了两套工具索引indexings[i]取单个字符切片slicings[start:stop:step]取一段。它们语法接近但出错行为完全不同——索引越界会抛IndexError切片越界却默默裁剪、绝不报错。这个不对称性正是很多隐蔽 bug 的来源。另一个必须建立的概念是str是不可变immutable类型。切片返回的永远是新字符串原串不会被改动。所以s[0] A会直接TypeError而s A s[1:]才是正确写法。理解「切片只读、结果新建」后面所有用法都会顺理成章。本文把索引和切片的规则逐条讲清重点标注负下标、负步长和边界值这三处最容易记错的地方。一、索引取单个字符s[i]返回位置i上的一个字符在 Python 3 里就是长度 1 的str# 适用于 Python 3.8s Pythonprint(s[0]) # Pprint(s[5]) # nprint(s[-1]) # n负数从尾部数print(s[-6]) # P负数下标的换算公式很简单s[-k]等价于s[len(s) - k]。所以len(s)为 6 时s[-1]就是s[5]。越界会抛异常# 适用于 Python 3.8s Python# s[6] # IndexError: string index out of range# s[-7] # IndexError空字符串用任何下标都越界包括s[0]和s[-1]。二、切片取一段切片的完整语法是s[start:stop:step]返回新字符串规则如下取值区间是左闭右开s[a:b]包含下标a不包含b。start省略默认0正步长或len(s)-1负步长。stop省略默认len(s)正步长或「到开头之前」负步长。step省略默认1。越界不报错会被裁剪到有效范围。# 适用于 Python 3.8s Pythonprint(s[0:2]) # Pyprint(s[2:5]) # thoprint(s[:3]) # Pytprint(s[3:]) # honprint(s[:]) # Python整串副本print(s[1:100]) # ython越界自动截断print(s[-3:]) # hon倒数三个print(s[:-3]) # Pyt去掉末尾三个s[:]得到一份内容相同的字符串常用于「复制一份」# 适用于 Python 3.8s abct s[:]print(t s) # True内容相等这里要提醒一句不要用is去判断两个字符串是否相等。is比较的是对象身份而str是否共用同一对象属于实现细节涉及字符串驻留CPython 的不同版本、不同构造方式都可能给出不同结果。判断内容一律用。三、步长与负步长step控制每隔几个字符取一个step为负时遍历方向反过来# 适用于 Python 3.8s abcdefprint(s[::2]) # ace隔一个取一个print(s[1::2]) # bdf从下标 1 开始隔一个取print(s[::-1]) # fedcba整体反转print(s[4:1:-1]) # edc从下标 4 倒着走到下标 2print(s[::-2]) # fdb负步长时start和stop的默认值会「对调」起点默认是末尾终点默认是开头之前。这正是s[::-1]能反转的原因。用一张表把常见组合列出来以abcdef为例切片含义结果s[:]全部abcdefs[2:]从 2 到末尾cdefs[:2]开头到 2不含abs[-2:]最后两个efs[1:4]下标 1、2、3bcds[::2]隔一取一aces[::-1]反转fedcbas[4:1:-1]4、3、2 倒序edcs[10:20]超界空四、越界与空结果的边界行为这一节是本篇的重点索引越界报错切片越界不报错。# 适用于 Python 3.8s abc# 索引越界即错误try:s[10]except IndexError as e:print(索引越界:, e)# 切片越界被裁剪没事发生print(s[10:20]) # 空字符串print(s[-100:2]) # abprint(s[2:1]) # 空字符串start stop 且正步长三种情况下切片都返回空字符串或裁剪后的结果从不抛异常start超出末尾stop小于start正步长start或stop是很大的负数。记住这条规则的实际价值写「取前 N 个字符」这类代码时不必先判长度。# 适用于 Python 3.8def head(s, n):取前 n 个字符n 超过长度时返回整串不报错。return s[:n]print(head(abc, 2)) # abprint(head(abc, 100)) # abcprint(head(, 5)) # 空五、索引和切片之外定位与拆分取字符之外常用的还有「找位置」和「按分隔符拆」# 适用于 Python 3.8s namevalueprint(s.find()) # 4找不到返回 -1print(s.find(!)) # -1print(s.index()) # 4# s.index(!) # ValueError: substring not foundkey, sep, value s.partition()print(key, value) # name valuefind与index的唯一区别就是「找不到时」find返回-1index抛ValueError。需要「先判断再取」时优先用find避免异常控制流。六、实战从路径里提取各部分# 适用于 Python 3.8def split_path(path):不依赖 os.path用切片和 rfind 拆出目录、文件名、扩展名。if not path:return , , slash path.rfind(/)directory path[:slash 1] if slash ! -1 else filename path[slash 1:]dot filename.rfind(.)if dot 0: # 没有点或点在开头隐藏文件return directory, filename, return directory, filename[:dot], filename[dot:]def mask_phone(phone):保留前 3 位和后 4 位中间打码。if len(phone) 7:return phonereturn phone[:3] * * (len(phone) - 7) phone[-4:]if __name__ __main__:print(split_path(/home/user/data.csv))print(split_path(README)) # (, README, )print(split_path(.bashrc)) # (, .bashrc, )print(mask_phone(13812345678)) # 138****5678输出(/home/user/, data, .csv)(, README, )(, .bashrc, )138****5678split_path用rfind拿到最后一个斜杠和最后一个点再用切片切开。dot 0这一判断同时处理了「没有扩展名」和「隐藏文件」两种情况——.bashrc的第一个点在下标 0不应该被当成扩展名分隔符。常见坑点以为切片越界会报错❌ 用try/except IndexError包住s[0:100]✅ 切片自动裁剪只有s[100]这种单下标才抛IndexError记错切片的开闭区间❌s[0:3]以为取 4 个字符 ✅ 切片是左闭右开s[0:3]取下标 0、1、2 共 3 个字符试图给字符串下标赋值❌s[0] A→TypeError: str object does not support item assignment✅s A s[1:]或重建整串负步长时按正步长的心智模型理解❌s[1:4:-1]以为能从 1 走到 3 ✅ 负步长要求start stop写反了得到空串比如s[4:1:-1]才是edcs[-0]当成「倒数第 0 个」❌s[-0]以为是从末尾数 ✅-0 0s[-0]就是s[0]要取最后一个用s[-1]用is判断两个字符串是否相等❌if s is ok:有时为真有时为假取决于字符串驻留这一实现细节 ✅ 一律用if s ok:比较内容用index却在找不到时崩溃❌s.index(x)在不存在时抛ValueError外部没接住 ✅ 不确定存在时用s.find(x)用if pos ! -1判断在循环里靠切片逐字拼接❌for i in range(len(s)): out s[i] out反复建新串 ✅ 整体用s[::-1]之类一次性切片或列表收集后.join总结操作写法越界行为返回取单字符s[i]抛IndexErrorstr长度 1取一段s[a:b]自动裁剪新str取一段带步长s[a:b:step]自动裁剪新str反转s[::-1]不适用新str复制s[:]不适用新str找位置安全s.find(sub)返回-1int找位置抛异常s.index(sub)抛ValueErrorint按分隔拆s.partition(sep)总能返回三元组tuple切片和索引的差别可以浓缩成一句话索引是「访问某个位置」越界就是错误切片是「划一个范围」范围外自动取舍。把左闭右开、负下标换算、负步长方向这三条记牢再加上对「不可变、结果新建」的认知字符串提取基本不会再踩坑。需要更高层的定位时find、partition、rfind通常比手算下标更稳妥。