第一部分初识正则表达式 (Regular Expressions)1.1 什么是正则表达式正则表达式通常缩写为 regex 或 regexp本质上是一种微型的、高度专业化的编程语言它被内嵌在Python语言中并通过re模块来提供。它不是Python独有的但Python的re模块为它提供了强大的实现。简单来说正则表达式是一套用于描述字符串模式的规则。你可以把它想象成一个超级通配符。Windows系统中的*.txt就是一个简单的模式而正则表达式可以定义远比这复杂得多的模式。1.2 为什么需要正则表达式正则表达式是处理文本数据的“瑞士军刀”。在数据清洗、文本提取、格式验证等场景中它几乎是不可或缺的工具。你可以用它来验证检查用户输入是否符合规则如邮箱、手机号、密码强度。查找从大段文本中提取所有符合特定模式的信息如所有网址、日期、IP地址。替换批量地将文本中符合模式的内容替换为其他内容如屏蔽敏感词、格式化日期。分割根据复杂的分隔符模式来拆分字符串。1.3 第一个正则表达式匹配字符最简单的正则表达式就是直接匹配自身。例如正则表达式test会精确地在文本中查找字符串test。但是正则表达式的威力在于它拥有元字符 (metacharacters)。这些特殊字符不匹配自身而是代表了特定的匹配规则。所有元字符列表如下我们将在后续部分逐一详解. ^ $ * ? { } [ ] \ | ( )第二部分正则表达式语法精要 (核心基础)这是正则表达式的基石必须熟练掌握。2.1 普通字符与元字符普通字符字母、数字、下划线等直接匹配自身如abc匹配 abc。元字符具有特殊含义的字符如.匹配任何单个字符(除换行符)。2.2 字符类 (Character Classes) -[...]使用方括号[...]定义一个字符集合匹配其中任意一个字符。示例[abc]匹配a,b或c。范围表示[a-z]匹配所有小写字母[0-9]匹配所有数字[a-zA-Z]匹配所有字母。排除 (Negation)在开头使用^如[11](ref)匹配除5之外的任何字符。元字符在字符类中绝大多数元字符如$,*在[]内部都会失去特殊含义变为普通字符例如[akm$]匹配a,k,m或$。2.3 预定义字符集 (Shorthand Character Classes)这些是常用字符集的快捷写法能很大程度上简化正则表达式。预定义字符等价于含义示例\d[0-9]匹配任意数字\d{3}匹配 123\D[^0-9]匹配任意非数字\D匹配 abc\w[a-zA-Z0-9_]匹配字母、数字、下划线 (ASCII模式)\w匹配 hello_123\W[^a-zA-Z0-9_]匹配非单词字符 (标点、空格等)\W匹配 \s[ \t\n\r\f\v]匹配任意空白字符 (空格、制表符、换行等)\s匹配多个空格\S[^ \t\n\r\f\v]匹配任意非空白字符\S匹配 hello.(除\n)匹配除换行符外的任意单个字符a.c匹配 abc, a1c\b-匹配单词边界 (单词与非单词字符的分界)\bcat\b精准匹配单词 cat\B-匹配非单词边界\Bcat\B匹配 concatenate 里的 cat重要提示在Python 3中\w默认会匹配Unicode中的字母字符如中文字符若只想匹配ASCII字符需使用re.ASCII标志。2.4 量词 (Quantifiers) - 控制重复次数量词用于指定前一个字符或分组必须出现的次数。量词含义贪婪/非贪婪示例 (a是示例字符)*匹配0次或更多次贪婪a*匹配 , a, aaa匹配1次或更多次贪婪a匹配 a, aaa?匹配0次或1次贪婪colou?r匹配 color, colour{n}匹配恰好n次固定\d{4}匹配 2026{n,m}匹配n到m次贪婪a{2,4}匹配 aa, aaa, aaaa{n,}匹配至少n次贪婪a{2,}匹配 aa, aaa...{,n}匹配最多n次贪婪a{,3}匹配 , a, aa, aaa2.5 贪婪 (Greedy) 与 非贪婪 (Non-greedy) 匹配这是一个极其重要的概念也是初学者容易犯错的地方。贪婪 (Greedy)默认情况下量词 (*,,?,{n,m}) 都是贪婪的它们会尽可能多地匹配字符。非贪婪 (Non-greedy / Lazy)在量词后面加上一个?就变成了非贪婪模式它们会尽可能少地匹配字符。示例对字符串div内容1/divspan内容2/span贪婪.会匹配div内容1/divspan内容2/span因为它从第一个开始一直吞到最后一个。非贪婪.?会匹配div内容1/div和span内容2/span因为它找到第一个就停止了。规则*?,?,??,{n,m}?都是非贪婪版本。2.6 位置锚点 (Anchors)锚点本身不匹配任何字符它们匹配的是字符串中的位置。^匹配字符串的开头。在多行模式(re.M)下也匹配每一行的开头。$匹配字符串的结尾。在多行模式下也匹配每一行的结尾。\b匹配单词边界即\w和\W之间的位置。\B匹配非单词边界。第三部分分组、捕获与引用分组是正则表达式提取结构化信息的核心能力。3.1 捕获分组 (Capturing Groups) -(...)使用圆括号(...)将模式的一部分包裹起来可以将其作为一个整体进行操作并且匹配到的内容会被“捕获”并存储起来供后续使用。import re text 生日: 1990-05-15 pattern r(\d{4})-(\d{2})-(\d{2}) match re.search(pattern, text) if match: print(f完整匹配: {match.group(0)}) # 1990-05-15 print(f年: {match.group(1)}) # 1990 print(f月: {match.group(2)}) # 05 print(f日: {match.group(3)}) # 15 print(f所有分组: {match.groups()}) # (1990, 05, 15)match.group(0)总是返回整个匹配项而group(1),group(2)... 返回从左到右的各个分组。3.2 命名分组 (Named Groups) -(?Pname...)当分组很多时用数字索引难以维护。命名分组允许我们为分组起一个名字。pattern r(?Pyear\d{4})-(?Pmonth\d{2})-(?Pday\d{2}) match re.search(pattern, text) if match: print(f年: {match.group(year)}) # 1990 print(f月: {match.group(month)}) # 05 print(f日: {match.group(day)}) # 153.3 反向引用 (Backreferences)反向引用允许你在同一个正则表达式内部引用之前已经捕获到的分组内容确保前后的内容一致。数字反向引用\1,\2... 引用对应编号的分组。命名反向引用(?Pname)引用对应的命名分组。示例查找重复的单词the the。pattern r\b(\w)\s\1\b re.search(pattern, the the quick brown fox) # 匹配成功示例匹配HTML标签tag.../tag。pattern r(?Ptag\w).*?/(?Ptag) re.search(pattern, bbold text/b) # 匹配成功性能警告反向引用会强制引擎进入回溯模式在处理长字符串时可能导致性能灾难甚至“灾难性回溯”。3.4 非捕获分组 (Non-capturing Groups) -(?:...)有时你只需要将一组模式作为一个整体应用量词但不需要捕获其内容。此时应使用(?:...)它可以节省资源并提高性能。# 匹配IP地址但不捕获每一段 pattern r(?:\d{1,3}\.){3}\d{1,3} text My IP is 192.168.1.1 print(re.findall(pattern, text)) # [192.168.1.1]第四部分re模块核心函数实战掌握了语法让我们看看如何在Python中使用它们。re模块提供了一整套函数。4.1re.match(pattern, string, flags0)从字符串的起始位置开始匹配。如果起始位置不匹配则返回None。print(re.match(rwww, www.runoob.com)) # 匹配成功返回Match对象 print(re.match(rcom, www.runoob.com)) # 匹配失败返回None场景验证字符串是否以某种模式开头如验证手机号格式^1[3-9]\d{9}$。4.2re.search(pattern, string, flags0)扫描整个字符串返回第一个成功的匹配。如果没找到返回None。print(re.search(rcom, www.runoob.com)) # 匹配成功区别match只从开头找search从任何位置找。这是两者最核心的区别。4.3re.findall(pattern, string, flags0)扫描整个字符串返回所有不重叠的匹配项的列表。如果没有匹配返回空列表。这是最常用的函数之一。text 电话: 138-1234-5678, 139-8765-4321 phones re.findall(r1[3-9]\d-\d{4}-\d{4}, text) print(phones) # [138-1234-5678, 139-8765-4321]4.4re.finditer(pattern, string, flags0)和findall类似但它返回的是一个迭代器 (iterator)迭代器中的每个元素是一个Match对象。这在处理大量匹配项时可以节省内存。for match in re.finditer(r\d, a1b2c3d4): print(f匹配值: {match.group()}, 位置: {match.span()})4.5re.sub(pattern, repl, string, count0, flags0)用于替换字符串中所有匹配正则表达式的子串。text 2024-10-01 是国庆节 # 将日期格式从 - 改为 / new_text re.sub(r(\d{4})-(\d{2})-(\d{2}), r\1/\2/\3, text) print(new_text) # 2024/10/01 是国庆节repl参数可以是字符串用\1,\2或\gname引用分组也可以是一个函数该函数接收一个Match对象并返回替换字符串功能非常强大。def double(matched): value int(matched.group(value)) return str(value * 2) s A23G4HFD567 print(re.sub((?Pvalue\d), double, s)) # A46G8HFD11344.6re.split(pattern, string, maxsplit0, flags0)根据匹配的子串来分割字符串返回一个列表。比字符串的split()方法更强大因为它可以按复杂模式分割。text apple,banana;orange|grape fruits re.split(r[,;|], text) print(fruits) # [apple, banana, orange, grape]4.7re.compile(pattern, flags0)预编译正则表达式生成一个Pattern对象。对于需要多次使用的同一正则表达式预编译可以显著提升性能因为它避免了每次调用match/search等函数时都重新编译正则。# 不推荐每次循环都编译 for text in large_texts: result re.search(r\d, text) # 推荐预编译只编译一次 pattern re.compile(r\d) for text in large_texts: result pattern.search(text)第五部分编译标志 (Flags)编译标志用于修改正则表达式的行为。它们可以作为参数传递给re.compile()或re.search()等函数也可以通过按位或 (|) 组合使用。标志缩写作用re.IGNORECASEre.I使匹配不区分大小写。如re.findall(rhello, text, re.I)可匹配 Hello, HELLO。re.MULTILINEre.M改变^和$的行为使它们匹配每一行的开头和结尾而不仅是整个字符串。对日志文件等按行分割的文本非常有用。re.DOTALLre.S使.元字符可以匹配换行符\n。默认情况下.是不能匹配换行的这在跨行匹配时经常用到。re.ASCIIre.A使\w,\d,\s等预定义字符集仅匹配ASCII字符而不是Unicode字符。例如\w将不再匹配中文字符。re.VERBOSEre.X允许在正则表达式中添加注释和空白使复杂的正则更具可读性。引擎会忽略模式中的空格和以#开头的注释。re.DEBUG无显示编译后的正则表达式模式用于调试和性能分析。re.VERBOSE实战处理复杂正则时这个标志是救星。phone_pattern re.compile(r (\d{3}) # 区号3位数字 [-.\s]? # 可选分隔符 (\d{3}) # 中间三位 [-.\s]? # 可选分隔符 (\d{4}) # 最后四位 , re.VERBOSE)内联标志你也可以在正则表达式模式内部启用标志影响整个或部分模式。例如(?i)启用忽略大小写。re.search(r(?i)python, Python) # 匹配成功第六部分进阶与实战技巧6.1 原子分组与占有量词 (Possessive Quantifiers)Python 标准库的re模块不支持原子分组(?...)和占有量词如*,但第三方regex模块支持。占有量词与贪婪量词类似但一旦匹配绝不会交还已经匹配的字符给后续模式尝试这可以极大地防止“灾难性回溯”。6.2 条件匹配同样re模块不支持但regex模块支持。可以根据前面的分组是否成功匹配来决定后续的模式。# regex模块示例匹配 (123) 456-7890 或 123-456-7890 pattern r(()?\d{3}(?(1))|-)\d{3}-\d{4}6.3 正则表达式的性能陷阱灾难性回溯 (Catastrophic Backtracking)这是正则表达式领域最著名的性能杀手。当正则表达式包含嵌套的量词如(a)b匹配一个无法匹配的字符串如 aaaa时引擎会进行指数级的尝试导致程序“假死”。如何避免具体化使用更具体的字符类代替.。使用非贪婪量词.*?有时能缓解但不能根治。元素化将(a)b改为ab。使用占有量词或原子分组如果使用regex模块。6.4 匹配中文在Python 3中\w可以匹配中文字符。若要精确匹配中文字符可以使用Unicode范围[\u4e00-\u9fff]。若要涵盖更多生僻字和中文标点可以扩宽范围如[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]。6.5 常用实战模式邮箱验证r^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$手机号 (中国大陆)r^1[3-9]\d{9}$URLrhttps?://[^\s/$.?#].[^\s]*IP地址r(?:\d{1,3}\.){3}\d{1,3}第七部分总结与最佳实践永远使用原始字符串r...。这是避免转义灾难的头号法则。对于复杂、多次使用的模式先re.compile()。性能提升巨大。复杂正则使用re.VERBOSE。加上注释和分行让你一个月后还能看懂自己写了什么。注意贪婪与懒惰。需要最小匹配时加?。优先使用re.search()而非re.match()除非你想明确匹配开头。利用捕获组提取信息比切片字符串更可靠。警惕灾难性回溯。如果你的正则运行缓慢很可能踩了这个坑。分解复杂验证。如果一个正则太复杂可以拆分成几个简单的步骤和正则。利用在线测试工具。在将正则表达式写入代码前先在 Regex101 等在线工具上调试完善。记住正则表达式是双刃剑。能用字符串方法解决的简单问题就别用正则。保持代码的可读性和可维护性。
Python 3正则表达式完全指南:从入门到精通
第一部分初识正则表达式 (Regular Expressions)1.1 什么是正则表达式正则表达式通常缩写为 regex 或 regexp本质上是一种微型的、高度专业化的编程语言它被内嵌在Python语言中并通过re模块来提供。它不是Python独有的但Python的re模块为它提供了强大的实现。简单来说正则表达式是一套用于描述字符串模式的规则。你可以把它想象成一个超级通配符。Windows系统中的*.txt就是一个简单的模式而正则表达式可以定义远比这复杂得多的模式。1.2 为什么需要正则表达式正则表达式是处理文本数据的“瑞士军刀”。在数据清洗、文本提取、格式验证等场景中它几乎是不可或缺的工具。你可以用它来验证检查用户输入是否符合规则如邮箱、手机号、密码强度。查找从大段文本中提取所有符合特定模式的信息如所有网址、日期、IP地址。替换批量地将文本中符合模式的内容替换为其他内容如屏蔽敏感词、格式化日期。分割根据复杂的分隔符模式来拆分字符串。1.3 第一个正则表达式匹配字符最简单的正则表达式就是直接匹配自身。例如正则表达式test会精确地在文本中查找字符串test。但是正则表达式的威力在于它拥有元字符 (metacharacters)。这些特殊字符不匹配自身而是代表了特定的匹配规则。所有元字符列表如下我们将在后续部分逐一详解. ^ $ * ? { } [ ] \ | ( )第二部分正则表达式语法精要 (核心基础)这是正则表达式的基石必须熟练掌握。2.1 普通字符与元字符普通字符字母、数字、下划线等直接匹配自身如abc匹配 abc。元字符具有特殊含义的字符如.匹配任何单个字符(除换行符)。2.2 字符类 (Character Classes) -[...]使用方括号[...]定义一个字符集合匹配其中任意一个字符。示例[abc]匹配a,b或c。范围表示[a-z]匹配所有小写字母[0-9]匹配所有数字[a-zA-Z]匹配所有字母。排除 (Negation)在开头使用^如[11](ref)匹配除5之外的任何字符。元字符在字符类中绝大多数元字符如$,*在[]内部都会失去特殊含义变为普通字符例如[akm$]匹配a,k,m或$。2.3 预定义字符集 (Shorthand Character Classes)这些是常用字符集的快捷写法能很大程度上简化正则表达式。预定义字符等价于含义示例\d[0-9]匹配任意数字\d{3}匹配 123\D[^0-9]匹配任意非数字\D匹配 abc\w[a-zA-Z0-9_]匹配字母、数字、下划线 (ASCII模式)\w匹配 hello_123\W[^a-zA-Z0-9_]匹配非单词字符 (标点、空格等)\W匹配 \s[ \t\n\r\f\v]匹配任意空白字符 (空格、制表符、换行等)\s匹配多个空格\S[^ \t\n\r\f\v]匹配任意非空白字符\S匹配 hello.(除\n)匹配除换行符外的任意单个字符a.c匹配 abc, a1c\b-匹配单词边界 (单词与非单词字符的分界)\bcat\b精准匹配单词 cat\B-匹配非单词边界\Bcat\B匹配 concatenate 里的 cat重要提示在Python 3中\w默认会匹配Unicode中的字母字符如中文字符若只想匹配ASCII字符需使用re.ASCII标志。2.4 量词 (Quantifiers) - 控制重复次数量词用于指定前一个字符或分组必须出现的次数。量词含义贪婪/非贪婪示例 (a是示例字符)*匹配0次或更多次贪婪a*匹配 , a, aaa匹配1次或更多次贪婪a匹配 a, aaa?匹配0次或1次贪婪colou?r匹配 color, colour{n}匹配恰好n次固定\d{4}匹配 2026{n,m}匹配n到m次贪婪a{2,4}匹配 aa, aaa, aaaa{n,}匹配至少n次贪婪a{2,}匹配 aa, aaa...{,n}匹配最多n次贪婪a{,3}匹配 , a, aa, aaa2.5 贪婪 (Greedy) 与 非贪婪 (Non-greedy) 匹配这是一个极其重要的概念也是初学者容易犯错的地方。贪婪 (Greedy)默认情况下量词 (*,,?,{n,m}) 都是贪婪的它们会尽可能多地匹配字符。非贪婪 (Non-greedy / Lazy)在量词后面加上一个?就变成了非贪婪模式它们会尽可能少地匹配字符。示例对字符串div内容1/divspan内容2/span贪婪.会匹配div内容1/divspan内容2/span因为它从第一个开始一直吞到最后一个。非贪婪.?会匹配div内容1/div和span内容2/span因为它找到第一个就停止了。规则*?,?,??,{n,m}?都是非贪婪版本。2.6 位置锚点 (Anchors)锚点本身不匹配任何字符它们匹配的是字符串中的位置。^匹配字符串的开头。在多行模式(re.M)下也匹配每一行的开头。$匹配字符串的结尾。在多行模式下也匹配每一行的结尾。\b匹配单词边界即\w和\W之间的位置。\B匹配非单词边界。第三部分分组、捕获与引用分组是正则表达式提取结构化信息的核心能力。3.1 捕获分组 (Capturing Groups) -(...)使用圆括号(...)将模式的一部分包裹起来可以将其作为一个整体进行操作并且匹配到的内容会被“捕获”并存储起来供后续使用。import re text 生日: 1990-05-15 pattern r(\d{4})-(\d{2})-(\d{2}) match re.search(pattern, text) if match: print(f完整匹配: {match.group(0)}) # 1990-05-15 print(f年: {match.group(1)}) # 1990 print(f月: {match.group(2)}) # 05 print(f日: {match.group(3)}) # 15 print(f所有分组: {match.groups()}) # (1990, 05, 15)match.group(0)总是返回整个匹配项而group(1),group(2)... 返回从左到右的各个分组。3.2 命名分组 (Named Groups) -(?Pname...)当分组很多时用数字索引难以维护。命名分组允许我们为分组起一个名字。pattern r(?Pyear\d{4})-(?Pmonth\d{2})-(?Pday\d{2}) match re.search(pattern, text) if match: print(f年: {match.group(year)}) # 1990 print(f月: {match.group(month)}) # 05 print(f日: {match.group(day)}) # 153.3 反向引用 (Backreferences)反向引用允许你在同一个正则表达式内部引用之前已经捕获到的分组内容确保前后的内容一致。数字反向引用\1,\2... 引用对应编号的分组。命名反向引用(?Pname)引用对应的命名分组。示例查找重复的单词the the。pattern r\b(\w)\s\1\b re.search(pattern, the the quick brown fox) # 匹配成功示例匹配HTML标签tag.../tag。pattern r(?Ptag\w).*?/(?Ptag) re.search(pattern, bbold text/b) # 匹配成功性能警告反向引用会强制引擎进入回溯模式在处理长字符串时可能导致性能灾难甚至“灾难性回溯”。3.4 非捕获分组 (Non-capturing Groups) -(?:...)有时你只需要将一组模式作为一个整体应用量词但不需要捕获其内容。此时应使用(?:...)它可以节省资源并提高性能。# 匹配IP地址但不捕获每一段 pattern r(?:\d{1,3}\.){3}\d{1,3} text My IP is 192.168.1.1 print(re.findall(pattern, text)) # [192.168.1.1]第四部分re模块核心函数实战掌握了语法让我们看看如何在Python中使用它们。re模块提供了一整套函数。4.1re.match(pattern, string, flags0)从字符串的起始位置开始匹配。如果起始位置不匹配则返回None。print(re.match(rwww, www.runoob.com)) # 匹配成功返回Match对象 print(re.match(rcom, www.runoob.com)) # 匹配失败返回None场景验证字符串是否以某种模式开头如验证手机号格式^1[3-9]\d{9}$。4.2re.search(pattern, string, flags0)扫描整个字符串返回第一个成功的匹配。如果没找到返回None。print(re.search(rcom, www.runoob.com)) # 匹配成功区别match只从开头找search从任何位置找。这是两者最核心的区别。4.3re.findall(pattern, string, flags0)扫描整个字符串返回所有不重叠的匹配项的列表。如果没有匹配返回空列表。这是最常用的函数之一。text 电话: 138-1234-5678, 139-8765-4321 phones re.findall(r1[3-9]\d-\d{4}-\d{4}, text) print(phones) # [138-1234-5678, 139-8765-4321]4.4re.finditer(pattern, string, flags0)和findall类似但它返回的是一个迭代器 (iterator)迭代器中的每个元素是一个Match对象。这在处理大量匹配项时可以节省内存。for match in re.finditer(r\d, a1b2c3d4): print(f匹配值: {match.group()}, 位置: {match.span()})4.5re.sub(pattern, repl, string, count0, flags0)用于替换字符串中所有匹配正则表达式的子串。text 2024-10-01 是国庆节 # 将日期格式从 - 改为 / new_text re.sub(r(\d{4})-(\d{2})-(\d{2}), r\1/\2/\3, text) print(new_text) # 2024/10/01 是国庆节repl参数可以是字符串用\1,\2或\gname引用分组也可以是一个函数该函数接收一个Match对象并返回替换字符串功能非常强大。def double(matched): value int(matched.group(value)) return str(value * 2) s A23G4HFD567 print(re.sub((?Pvalue\d), double, s)) # A46G8HFD11344.6re.split(pattern, string, maxsplit0, flags0)根据匹配的子串来分割字符串返回一个列表。比字符串的split()方法更强大因为它可以按复杂模式分割。text apple,banana;orange|grape fruits re.split(r[,;|], text) print(fruits) # [apple, banana, orange, grape]4.7re.compile(pattern, flags0)预编译正则表达式生成一个Pattern对象。对于需要多次使用的同一正则表达式预编译可以显著提升性能因为它避免了每次调用match/search等函数时都重新编译正则。# 不推荐每次循环都编译 for text in large_texts: result re.search(r\d, text) # 推荐预编译只编译一次 pattern re.compile(r\d) for text in large_texts: result pattern.search(text)第五部分编译标志 (Flags)编译标志用于修改正则表达式的行为。它们可以作为参数传递给re.compile()或re.search()等函数也可以通过按位或 (|) 组合使用。标志缩写作用re.IGNORECASEre.I使匹配不区分大小写。如re.findall(rhello, text, re.I)可匹配 Hello, HELLO。re.MULTILINEre.M改变^和$的行为使它们匹配每一行的开头和结尾而不仅是整个字符串。对日志文件等按行分割的文本非常有用。re.DOTALLre.S使.元字符可以匹配换行符\n。默认情况下.是不能匹配换行的这在跨行匹配时经常用到。re.ASCIIre.A使\w,\d,\s等预定义字符集仅匹配ASCII字符而不是Unicode字符。例如\w将不再匹配中文字符。re.VERBOSEre.X允许在正则表达式中添加注释和空白使复杂的正则更具可读性。引擎会忽略模式中的空格和以#开头的注释。re.DEBUG无显示编译后的正则表达式模式用于调试和性能分析。re.VERBOSE实战处理复杂正则时这个标志是救星。phone_pattern re.compile(r (\d{3}) # 区号3位数字 [-.\s]? # 可选分隔符 (\d{3}) # 中间三位 [-.\s]? # 可选分隔符 (\d{4}) # 最后四位 , re.VERBOSE)内联标志你也可以在正则表达式模式内部启用标志影响整个或部分模式。例如(?i)启用忽略大小写。re.search(r(?i)python, Python) # 匹配成功第六部分进阶与实战技巧6.1 原子分组与占有量词 (Possessive Quantifiers)Python 标准库的re模块不支持原子分组(?...)和占有量词如*,但第三方regex模块支持。占有量词与贪婪量词类似但一旦匹配绝不会交还已经匹配的字符给后续模式尝试这可以极大地防止“灾难性回溯”。6.2 条件匹配同样re模块不支持但regex模块支持。可以根据前面的分组是否成功匹配来决定后续的模式。# regex模块示例匹配 (123) 456-7890 或 123-456-7890 pattern r(()?\d{3}(?(1))|-)\d{3}-\d{4}6.3 正则表达式的性能陷阱灾难性回溯 (Catastrophic Backtracking)这是正则表达式领域最著名的性能杀手。当正则表达式包含嵌套的量词如(a)b匹配一个无法匹配的字符串如 aaaa时引擎会进行指数级的尝试导致程序“假死”。如何避免具体化使用更具体的字符类代替.。使用非贪婪量词.*?有时能缓解但不能根治。元素化将(a)b改为ab。使用占有量词或原子分组如果使用regex模块。6.4 匹配中文在Python 3中\w可以匹配中文字符。若要精确匹配中文字符可以使用Unicode范围[\u4e00-\u9fff]。若要涵盖更多生僻字和中文标点可以扩宽范围如[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]。6.5 常用实战模式邮箱验证r^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$手机号 (中国大陆)r^1[3-9]\d{9}$URLrhttps?://[^\s/$.?#].[^\s]*IP地址r(?:\d{1,3}\.){3}\d{1,3}第七部分总结与最佳实践永远使用原始字符串r...。这是避免转义灾难的头号法则。对于复杂、多次使用的模式先re.compile()。性能提升巨大。复杂正则使用re.VERBOSE。加上注释和分行让你一个月后还能看懂自己写了什么。注意贪婪与懒惰。需要最小匹配时加?。优先使用re.search()而非re.match()除非你想明确匹配开头。利用捕获组提取信息比切片字符串更可靠。警惕灾难性回溯。如果你的正则运行缓慢很可能踩了这个坑。分解复杂验证。如果一个正则太复杂可以拆分成几个简单的步骤和正则。利用在线测试工具。在将正则表达式写入代码前先在 Regex101 等在线工具上调试完善。记住正则表达式是双刃剑。能用字符串方法解决的简单问题就别用正则。保持代码的可读性和可维护性。