前言字符串string是日常写 Python 打交道最多的类型。标题里列出了七类操作——替换、删除、截取、复制、连接、比较、查找——它们覆盖了str方法的大半江山也恰好集中了最容易被记错的几个点。先纠正一个前提性误解str是不可变immutable的。所谓「替换」「删除」其实没有一个方法能改动原字符串所有返回字符串的方法都是返回一个新字符串原对象纹丝不动。理解这一点后面所有的行为都能推导出来既然不能改那么「原地修改」不存在「复制」这件事对字符串也就没有深浅之分——赋值和切片得到的都指向同一个不可变对象安全得很。第二个误解是「字符串切片越界会报错」。实际规则是分成两半切片越界不报错索引越界才报错。abc[10:20]老老实实返回空字符串而abc[10]立刻IndexError。这条差别是很多 bug 的源头。本文按标题的顺序把七类操作逐个讲清并标注容易记错的细节。一、查找find / index / count / in查找分「找位置」和「判断存在」两种需求官方文档明确建议只想知道子串在不在用in需要位置时才用find。写法找不到时返回s.find(sub)返回-1首次出现的下标s.index(sub)抛ValueError首次出现的下标s.rfind(sub)/s.rindex(sub)同上最后一次出现的下标sub in s返回FalseTrue/Falses.count(sub)返回0出现次数三种方法都接受可选的start、end语义和切片一致# 适用于 Python 3.8s spam, spam, spamprint(s.find(sp)) # 0print(s.find(sp, 5)) # 6print(s.find(egg)) # -1print(s.count(spam)) # 3print(s.startswith(spam)) # Trueprint(s.endswith((am, gg))) # True —— 可以传元组startswith()/endswith()的第一个参数可以是一个元组只要匹配其中任意一个就返回True写多个分支判断时很方便。二、截取切片是主力「截取」在 Python 里就是切片slicing语法s[start:stop:step]三个部分都可省略# 适用于 Python 3.8s Pythonprint(s[1:4]) # ythprint(s[:3]) # Pytprint(s[3:]) # honprint(s[-2:]) # on 负索引从末尾数print(s[::-1]) # nohtyP 步长为负即反转print(s[::2]) # Pto 隔一个取一个越界行为是这里的关键切片的下标超出范围会被自动裁剪不报错。# 适用于 Python 3.8s abcprint(s[10:20]) # —— 越界切片得到空串print(s[100]) # IndexError: string index out of rangeprint(s[-100:]) # abc —— 负向越界也会被裁剪因此「安全取子串」用切片而「访问单个字符」必须先判断长度。s[0]得到的是长度为 1 的字符串Python 里没有单独的字符类型。三、替换replace 与 translatestr.replace(old, new, count-1)返回新字符串把old全部替换成new给了count就只替换前count次。注意count在 Python 3.13 之前只能是位置参数3.13 起才支持关键字形式。# 适用于 Python 3.8s spam, spam, spamprint(s.replace(spam, eggs)) # eggs, eggs, eggsprint(s.replace(spam, eggs, 1)) # eggs, spam, spamprint(s) # spam, spam, spam —— 原串没变要做逐字符替换比如大小写转换、删掉某几个字符用translate()配maketrans()# 适用于 Python 3.8table str.maketrans(abc, 123, ) # a-1, b-2, c-3, 空格删除print(a b c.translate(table)) # 123str.maketrans()有三参数的静态方法形式maketrans(from, to, remove)也有单参数的字典形式maketrans({...})。replace是「子串替换」translate是「字符映射」两者别混用。四、删除strip、removeprefix、removesuffixstrip()家族删的是两端属于某个字符集合的字符不是删「前缀」# 适用于 Python 3.8print( spacious .strip()) # spaciousprint(www.example.com.strip(cmowz.)) # examplestrip(cmowz.)的参数是字符集合不是前缀串所以它会从两端一直删直到遇到不在集合里的字符为止。这点是最大的误会来源。真正按「前缀/后缀」删的方法在Python 3.9 及以上才有# 适用于 Python 3.9print(TestHook.removeprefix(Test)) # Hookprint(BaseTestCase.removeprefix(Test)) # BaseTestCase —— 不匹配就原样返回print(MiscTests.removesuffix(Tests)) # Misc3.9 之前要自己用切片实现# 适用于 Python 3.8 及更早s TestHookprefix Testif s.startswith(prefix):s s[len(prefix):]三个方法的对比方法删除依据是否按完整前缀strip(chars)两端的字符集合否逐字符判断lstrip/rstrip左端 / 右端字符集合否removeprefix(p)完整前缀且只删一次3.9是removesuffix(s)完整后缀且只删一次3.9是五、复制与连接复制字符串不可变s2 s已经足够安全任何一方都不会影响另一方。写s[:]或s 去「深拷贝」纯属多余。唯一要注意的是字符串没有.copy()方法——那是我给读者挖的假想方法别去找。连接拼两个字符串*重复多个字面量相邻会自动合并# 适用于 Python 3.8print(Py thon) # Pythonprint(ab * 3) # abababprint(abc def) # abcdef —— 字面量隐式拼接仅限字面量sep , print(sep.join([a, b, c])) # a, b, c拼接效率的常识反复拼接大量片段理论上是 O(n²)因为每次都要新建字符串并复制已有内容。推荐做法是把片段收集进列表最后.join(parts)一次性拼出结果join在结构上是 O(n)。需要说明的是CPython 对「对同一个变量反复」有一层就地扩容的优化实际表现常常接近线性但这属于实现细节、不是语言保证跨解释器如 PyPy就可能退化所以仍应以join为准。本文不给具体倍数读者可用timeit自行对比。# 适用于 Python 3.8parts [str(i) for i in range(1000)]text .join(parts) # 推荐六、比较比内容is比身份是不是同一个对象。字符串驻留interning是实现细节不要依赖is判断字符串相等# 适用于 Python 3.8a hellob hel loprint(a b) # True —— 内容相同print(a is b) # 不确定别依赖排序比较用的是码点顺序所以大写字母排在小写字母前面Z a为真。做大小写无关比较时用casefold()而不是lower()casefold是为「无差别比较」设计的对德语 ß、土耳其语等处理更彻底。# 适用于 Python 3.8print(Straße.lower().casefold()) # strasseprint(ABC.lower() abc) # True比较还可用于排序sorted(words)按码点升序sorted(words, keystr.casefold)忽略大小写。常见坑点把 strip 当成删前缀❌www.example.com.strip(www.)以为得到example.com实际得到example把 e、x、a、m、p、l 也删了✅ 删完整前缀用 3.9 的removeprefix(www.)旧版用s[len(prefix):]配startswith判断混淆 find 与 index 的失败行为❌ 用s.find(x) -1判断「没找到」写成或用s.index(x)后忘了它可能抛异常✅ 判断存在用x in sfind失败返回-1index失败抛ValueError按需选以为切片越界会报错❌abc[5]用来「安全取字符」结果IndexError✅ 单字符用索引并先判断长度取子串用切片切片越界返回空串以为 replace 会改原串❌s.replace(a, b)之后直接用s发现没变✅ 接住返回值s s.replace(a, b)用 is 比较字符串❌if name is admin:在不同运行环境结果可能不同✅if name admin:join 顺序写反❌[a, b].join(-)会报AttributeError因为列表没有 join✅-.join([a, b])分隔符是调用者忘了 join 要求元素是字符串❌.join([1, 2])→TypeError✅.join(str(x) for x in [1, 2])用 lower() 做大小写无关比较❌if a.lower() b.lower():对某些特殊字符不够彻底✅if a.casefold() b.casefold():总结操作首选写法记忆点查找sub in s/s.find(sub)find失败返回 -1index抛异常截取s[a:b]越界不报错负索引从末尾数替换s.replace(old, new, count)返回新串原串不变删除strip(chars)/ 3.9 的removeprefix前者是字符集合后者才是前缀复制s2 s不可变无需拷贝连接sep.join(parts)优于大量比较/casefold()is是实现细节不要用把这七类操作串起来看会发现一条主线字符串不可变。所有方法都返回新值原对象永远不变由此推导出的切片安全、赋值为共享、拼接要换join都是同一个事实的不同侧面。记住这条主线再记准strip的字符集合语义和find/index的失败差别字符串操作就基本不会写错了。