前言
"字符替换的四方法"这个说法来自博客和问答站的归纳,并不是 Python 官方规定的分类——官方文档里没有一个章节叫"四种替换方法"。所以本文要先把话说清楚:Python 里做替换的手段不止四种,也不止于四种,所谓"四方法"只是把最常用的几种挑出来讲。为了避免被这个数字带偏,本文按用途来分,把主流四种讲透,同时点出各自的边界。
另一个普遍误解是把str.replace当成"正则替换"。它只是普通的子串替换:第一个参数是字面量字符串,.就是点号,\d就是反斜杠加 d,没有任何特殊含义。想要正则语义,必须换re.sub。反过来,能用str.replace解决的简单替换,硬上正则既慢又难读。
本文代码基于 Python 3.8 及以上。Python 2.7 已于 2020 年 1 月 1 日停止维护,文中不采用任何 Python 2 专有写法。
一、str.replace:最简单的一对一替换
官方签名是:
str.replace(old, new, count=-1)返回一个新字符串,把所有出现的子串old换成new。count给定且非负时,只替换前count次;不传或传-1时全部替换。
# 适用于 Python 3.8+
s = "hello, world"
print(s.replace("l", "L")) # heLLo, worLd,全部替换
print(s.replace("l", "L", 1)) # heLlo, world,只换第一个
print(s) # hello, world,原串不变关键在于old、new都是字面量。s.replace(".", "-")会把每个点号换成连字符,而不是"匹配任意字符"。
二、re.sub:正则替换
需要按模式匹配时,用re模块的sub:
re.sub(pattern, repl, string, count=0, flags=0)count=0表示替换所有匹配,这点和str.replace用-1表示全部恰好相反,很容易记混。repl可以是字符串,也可以是一个接收匹配对象的函数。
# 适用于 Python 3.8+
import re
s = "a1b22c333"
print(re.sub(r"\d+", "#", s)) # a#b#c# 把连续数字整段换成 #
print(re.sub(r"\d", "#", s)) # a#b##c### 逐个数字换
# repl 传函数:把每个数字翻倍
print(re.sub(r"\d", lambda m: str(int(m.group()) * 2), s))
# a2b44c666repl是字符串时,\1、\g<名字>这类写法表示反向引用分组;正因为反斜杠有特殊含义,替换串里想放真反斜杠需要写成\\。
re模块还提供了一个re.subn(pattern, repl, string, count=0, flags=0):行为和sub一样,但返回的是「替换后的字符串」和「替换了多少次」组成的二元组,需要统计替换次数时用它。
# 适用于 Python 3.8+
import re
new_s, n = re.subn(r"\d+", "#", "a1b22c333")
print(new_s) # a#b#c#
print(n) # 3另外,flags参数可以传re.IGNORECASE等标志,让匹配忽略大小写:
# 适用于 Python 3.8+
import re
print(re.sub("abc", "X", "ABCabc", flags=re.IGNORECASE)) # XX三、str.translate + str.maketrans:单字符映射批量替换
要一次替换很多个单个字符(比如全角的标点转半角、把一串元音删掉),translate是最合适的。它配合maketrans用一个翻译表工作:
static str.maketrans(from, to, remove='', /)
static str.maketrans(dict, /)
str.translate(table, /)translate本身不接收字符串参数,只接收一个"码位到码位(或字符串/None)的映射表",这个表通常由maketrans生成。
# 适用于 Python 3.8+
# 一对一映射:a→x, b→y, c→z
table = str.maketrans("abc", "xyz")
print("abcabc".translate(table)) # xyzxyz
# 删除字符:第三个参数列出要删掉的字符
table2 = str.maketrans("", "", "aeiou")
print("hello world".translate(table2)) # hll wrldmaketrans("abc", "xyz")得到的其实是类似{97: 120, 98: 121, 99: 122}的字典,键和值都是 Unicode 码位。这也解释了它的边界:translate 只能做单字符级别的映射,没法把一个多字符子串"ab"整体换成"xy"。
四、切片拼接:按位置定点替换
有时候要替换的不是"某个内容的全部出现",而是"某个固定位置的一段"。这时用切片拼出结果:
# 适用于 Python 3.8+
def replace_slice(s, start, end, new):
return s[:start] + new + s[end:]
print(replace_slice("2024-01-01", 5, 7, "12")) # 2024-12-01因为str不可变,所谓"定点替换"只能靠"前缀 + 新内容 + 后缀"生成新串,这也是本文所有方法返回新串的根本原因。
四种方法对比
| 方法 | 匹配方式 | 粒度 | 典型用途 |
|---|
str.replace | 字面子串 | 子串 | 简单文本替换 |
re.sub | 正则 | 任意模式 | 按模式、条件替换 |
str.translate | 单字符映射表 | 单个字符 | 批量字符互换 / 删除 |
| 切片拼接 | 按位置 | 区间 | 定点替换 |
实战:清洗用户输入的一段文本
把四种方法放到同一个清洗流程里,各管一段:
# 适用于 Python 3.8+
import re
def clean_text(raw):
# 1) 用 translate 把全角空格和制表符统一成普通空格
t = str.maketrans(" \t", " ")
raw = raw.translate(t)
# 2) 用 str.replace 去掉不想留下的字面量标记
raw = raw.replace("[已删除]", "")
# 3) 用 re.sub 合并连续空白为一个空格
raw = re.sub(r"\s+", " ", raw)
# 4) 用切片去掉首尾各一个多余的空格
if raw.startswith(" "):
raw = raw[1:]
if raw.endswith(" "):
raw = raw[:-1]
return raw
print(clean_text(" hello\t\t[已删除] world "))
# hello world这段代码里,\s+会把连续空白压成一个空格,translate负责在正则之前把全角空格这类"看着像空格但不是 ASCII 空格"的字符归拢过来。步骤 4 也可以用strip()一步完成,这里写成切片是为了演示定点处理。
常见坑点
- 拿正则当 str.replace 的实参
❌"a1b2".replace(r"\d", "#"),结果还是a1b2,因为它是字面子串
✅ 用正则就换import re; re.sub(r"\d", "#", "a1b2")
- 记错"全部替换"的取值
❌re.sub(r"\d", "#", s, -1),以为-1是全替换
✅re.sub用count=0表示全部;str.replace才是用-1或省略
- 忘了 old 为空串的后果
❌"abc".replace("", "-"),结果是-a-b-c-,每个字符缝里都插了一段
✅ 空串替换前先判断,非必要不要用空old
- 以为 translate 能换多字符子串
❌str.maketrans("ab", "xy")想一次把子串"ab"换成"xy"
✅ 它只按单个字符映射;多字符整体替换要用str.replace或re.sub
- 改动没接住返回值
❌s.replace("a", "b")单独一行,以为s变了
✅s = s.replace("a", "b"),str方法返回新串
- 替换串里的反斜杠有特殊含义
❌ 想插入一个字面量反斜杠却只写一个,或者在替换串里把\1当成普通字符
✅repl里的\1、\g<name>是分组引用;要放字面量反斜杠得写成两个,拿不准时改用函数式repl彻底避开转义
- 用 translate 时把映射表搞反
❌str.maketrans("xyz", "abc")却当成 a→x 使用
✅maketrans(from, to)是 from→to,"abc"在from位才是 a→x
- 照抄 Python 2 的字符处理
❌ 用unicode、basestring或string.replace(s, old, new)的旧式调用
✅ Python 3 里统一是str,方法用s.replace(old, new);Python 2.7 已于 2020 年 1 月 1 日 EOL
总结
| 需求 | 首选 |
|---|
| 替换一段固定文字 | str.replace(old, new, count=-1) |
| 按模式或条件替换 | re.sub(pattern, repl, string, count=0) |
| 批量换/删单个字符 | str.translate配str.maketrans |
| 替换固定位置的一小段 | 切片拼接 |
所谓"四方法"只是习惯归纳,真正要记的是匹配方式决定选哪个工具:字面量走str.replace,模式走re.sub,单字符走translate,位置走切片。另外别忘了一个共性——它们全都返回新字符串,str不可变,没有哪一次替换是"就地进行"的。