2026/10/12 6:28:48

Python 正则表达式中的`^`和`[]`及常见含义

Python 正则表达式中的`^`和`[]`及常见含义 前言^和[]是正则里出镜率最高、也最容易理解错的两个符号。^在不同位置有完全不同的含义写在模式开头是「串首锚点」写在方括号内的第一个位置是「取反」写在方括号内的其它位置又只是普通的尖角号字符。[]则是「字符集」它内部有一套自己的规则——哪些字符有特殊含义、-怎么表示范围、]怎么放进去都和方括号外不一样。很多人把它们混着用的后果是想「匹配一个非数字开头的串」却写成^[0-9]结果语义正好反了。本文把这两个符号在类内和类外的行为逐条对照配可运行的例子说清楚。示例基于 Python 3.8。另有必要提一句Python 2.7 已于 2020 年 1 月 1 日停止维护在 Python 2 里字符串默认是 bytes\d、\w只认 ASCII想看 Unicode 语义还得额外加re.UNICODE这些差异在 Python 3 里已经默认帮你处理好了。一、^的三副面孔先看^写法^的位置含义^abc方括号外锚点匹配字符串开头[^abc]方括号内首位取反匹配 a、b、c 之外的字符[a^bc]方括号内非首位普通字符^# 适用于 Python 3.8import re# 类外锚点只匹配串首print(re.findall(r^#.*, a\n# b\n# c)) # [] 默认只认整串开头print(re.findall(r^#.*, a\n# b\n# c, re.M)) # [# b, # c] 按行# 类内首位取反print(re.findall(r[^#], #a#b)) # [a, b]# 类内非首位字面量print(re.findall(r[a#b], #a#b)) # [#, a, #, b]第一行返回空列表正是新手最容易懵的点^默认只匹配整个字符串的开头不是每一行。要让它对每一行生效必须加re.MULTILINE行内写(?m)。再看和^对应的$。$匹配字符串末尾加了re.MULTILINE后匹配每一行末尾它还有一个微妙之处——默认情况下$也匹配「结尾换行符之前」的位置# 适用于 Python 3.8import reprint(re.search(rfoo$, foo\n)) # 匹配成功$ 认末尾换行前的位置print(re.search(rfoo\Z, foo\n)) # None\Z 要求正好在字符串末尾\Z表示「字符串绝对末尾」从 Python 3.14 起还新增了等价的\z\Z与之相同。二、[]字符集类内的规则方括号建立一个「字符集」匹配其中任意一个字符。类内的规则和类外差别很大目标写法说明枚举若干字符[abc]a、b、c 之一表示范围[a-z]小写字母组合范围[A-Za-z0-9_]字母、数字、下划线取反[^0-9]非数字字面量-[-az]或[a\-z]放首位 / 末位 / 转义字面量][]a]或[a\]]放首位 / 转义字面量.[.]类内 * ? .都失去特殊含义# 适用于 Python 3.8import reprint(re.findall(r[A-Za-z0-9_], id_1 AND x9)) # [id_1, AND, x9]print(re.findall(r[a\-z], a-z)) # [a, -, z]print(re.findall(r[-az], a-z)) # [a, -, z]# 类外的 . 是「任意字符」类内的 [.] 只匹配真正的点号print(re.findall(r., a.b)) # [a, ., b]print(re.findall(r[.], a.b)) # [.]记住两条类内的 * ? . ( )等特殊符号都退化成普通字符唯一还保留转义能力的是反斜杠所以[\d]、[\w]、[\s]、[\n]、[\x41]在类内依然有效。# 适用于 Python 3.8import reprint(re.findall(r[\d], a1b2)) # [1, 2]print(re.findall(r[\w], a-1_b)) # [a, 1_b]三、^与[]组合时的坑把^和[]拼在一起是高频操作也是最容易写反的地方。对照下面两行# 适用于 Python 3.8import res 3 applesprint(re.findall(r^[0-9], s)) # [3] 以数字开头print(re.findall(r[^0-9], s)) # [ , a, p, p, l, e, s] 所有非数字^[0-9]是「开头位置紧接一个数字」只可能返回一个字符[^0-9]是「任意非数字字符」会把整串里的非数字全找出来。两者语义完全不同只有一个方括号的位置之差。另一个常见误区是范围的端点。[A-z]看着像「所有字母」但它包含的是 ASCII 码 65 到 122 之间的全部字符中间夹着[ \ ] ^ _ \ 这些标点# 适用于 Python 3.8import reprint(re.findall(r[A-z], aZ_[])) # 下划线、方括号都被吃了进来要「所有字母」请老老实实写[A-Za-z]别用[A-z]偷懒。四、类内可用的转义方括号内支持的转义和类外大体一致但有两处要注意\b在类外是单词边界在类内表示退格字符backspace。两者含义完全不同。\uXXXX、\UXXXXXXXX、\N{名称}这些在 Unicodestr模式里有效在 bytes 模式里会报错。# 适用于 Python 3.8import reprint(re.findall(r[一-鿿], 中文abc)) # [中文][一-鿿]和直接用中文字符写的[一-鿿]等价写转义形式在源码里更直观地表明「这是按码位圈定的范围」。五、两个容易被忽略的细节细节一VERBOSE 模式下的空白。re.VERBOSE或行内(?x)会忽略模式里的空白和#注释方便排版但字符集内部的空白是有意义的不受这个规则影响# 适用于 Python 3.8import re# (?x) 下模式里的空格被忽略但 [ ] 里的空格仍然只匹配空格print(re.findall(r(?x) [ ] # 匹配一个空格, a b)) # [ ]正因为如此在VERBOSE模式里想匹配一个真正的空格写[ ]比写转义的\更不容易看漏。细节二字符集不能为空。单独的[]并不是「空集合」而是「未闭合」——因为紧跟[的]会被当成字面量成员于是整个字符集没有收尾的]编译直接报错# 适用于 Python 3.8import retry:re.compile([])except re.error as e: # 3.13 起别名到 re.PatternErrorprint(出错, e)print(re.findall(r[]], a]b)) # []]首位 ] 被当作字面量要匹配]本身就把它放在方括号内首位写成[]]或者写成[\]]。常见坑点❌ 想匹配「非数字」写成^[0-9]实际只匹配「以数字开头的串」。✅ 取反的^必须放进方括号首位[^0-9]。❌ 用^做按行匹配却忘了加re.MULTILINE只匹配到第一行。✅ 按行匹配要re.MULTILINE或行内(?m)否则^只认整串开头。❌ 用[A-z]代替[A-Za-z]把[ \ ] ^ _ \ 也纳入了匹配。✅ 大写和小写各写一段范围[A-Za-z]。❌ 在字符集里写]想匹配右方括号结果它提前结束了字符集模式语法报错。✅ 把]放在方括号内首位[]a]或用反斜杠转义[a\]]。❌ 写[a-z-0-9]这类多个范围连在一起-被当成范围符号结果出乎意料。✅ 字面量-放到首位或末位[-a-z0-9]或写成\-。❌ 以为类外的.和类内的[.]一样用[.]去「匹配任意字符」结果只匹配点号。✅ 任意字符用.配re.S可跨行只匹配点号才用[.]或\.。❌ 在字符集里写[\b]想表达单词边界实际匹配的是退格字符。✅ 单词边界\b只能用在类外类内它是退格。❌ 认为$和\Z完全等价处理「结尾带换行」的文本时匹配范围对不上。✅$默认也认末尾换行符之前的位置\Z以及 3.14 的\z只认字符串绝对末尾。总结符号类外含义类内首位类内非首位^串首锚点取反字面量^$串尾锚点字面量$字面量$.任意字符字面量.字面量.-字面量字面量表示范围]字面量字面量结束字符集一句话记住^的含义取决于它在不在方括号里、以及是不是方括号内的第一个字符。写模式时先把方括号的边界在脑子里圈出来^是锚点还是取反就一目了然^[0-9]和[^0-9]这种一字之差也不会再写反。