当前位置: 首页 > news >正文

【Python】正则表达式

目录

  • 正则表达式
    • 基本概念
    • re 模块常用函数
    • 正则表达式修饰符(Flags)
    • 高级用法
    • 应用案例

正则表达式

基本概念

什么是正则表达式?

正则表达式(Regex Expressions)是一种 用来匹配和处理字符串的规则表达式。它不是编程语言,但几乎所有编程语言都支持它(Python、Java、JavaScript、C#、Go……)。

直观上,正则表达式就是一种 字符串模式(pattern)
比如:

  • \d{3}-\d{4}-\d{4} →\to 可以匹配一个中国手机号的格式(xxx-xxxx-xxxx)。
  • ^[A-Za-z0-9_]{6,12}$ →\to 可以匹配一个 6–12 位的账号名(只允许字母、数字和下划线)。

换句话说:正则表达式是 描述字符串特征的“公式”

Python 的原始字符串(Raw String)

在正则里,很多特殊符号都用 \(反斜杠)开头。但 Python 自己也用 \ 作为转义符(比如 \n 换行)。
为了避免冲突,Python 提供了 原始字符串

normal = "Hello\nWorld"
print(normal)   # 换行raw = r"Hello\nWorld"
print(raw)      # 输出 Hello\nWorld

所以写正则时,几乎总是用 r'...'

正则表达式的基本组成

正则表达式是由一些 元字符(meta characters)普通字符 组成的。

  1. 普通字符

    普通字符表示 自身

    • abc →\to 匹配 “abc”。

    • 123 →\to 匹配 “123”。

  2. 元字符(特殊符号)

    元字符是正则表达式的“魔法”,用来表示特殊意义。

    • 匹配字符类

      符号含义示例
      .匹配除换行符以外的任意单个字符a.c 可以匹配 “abc”, “axc”
      \d数字 [0-9]\d\d\d → “123”
      \w单词字符 [A-Za-z0-9_]\w+ → “hello_123”
      \s空白符(空格、制表符、换行)a\sb 可以匹配 “a b”
      \D非数字
      \W非单词字符
      \S非空白符
    • 数量限定符

      符号含义示例
      *匹配 0 次或多次ab* 可以匹配 “a”, “ab”, “abbb”
      +匹配 1 次或多次ab+ 匹配 “ab”, “abbb”,但不匹配 “a”
      ?匹配 0 次或 1 次ab? 匹配 “a” 或 “ab”
      {n}恰好 n 次\d{4} 匹配 “2025”
      {n,}至少 n 次\d{2,} 匹配 “12”, “12345”
      {n,m}n 到 m 次a{2,4} 匹配 “aa”, “aaa”, “aaaa”
    • 位置锚点

      符号含义示例
      ^匹配字符串开头^abc 只能匹配以 “abc” 开头的字符串
      $匹配字符串结尾abc$ 匹配以 “abc” 结尾的字符串
      \b单词边界\bcat\b 匹配 “cat”,但不匹配 “category”
      \B非单词边界
    • 分组和选择

      符号含义示例
      ()分组(abc){2} 匹配 “abcabc”
      ```
      []字符集[abc] 匹配 “a” 或 “b” 或 “c”
      [^]非字符集[^0-9] 匹配非数字字符

匹配原理

正则表达式的底层原理基于 有限自动机(Finite Automaton, FA)
你可以简单理解为:

  1. 正则是一个 规则引擎
  2. 引擎会从字符串开头扫描,尝试匹配模式。
  3. 如果当前路径失败,会 回溯(backtracking),尝试别的匹配方式。
  4. 成功 → 返回匹配结果;失败 → 返回 “不匹配”。

举个例子:
正则:ab*c
匹配字符串:abbbc

  • 从左到右扫描:
    • 匹配到 a
    • 匹配到 b* → 有 3 个 “b” ✅
    • 匹配到 c
  • 完全匹配成功。

常见应用场景

  1. 数据验证
    • 邮箱:^[\w.-]+@[\w.-]+\.[A-Za-z]{2,6}$
    • 手机号:^1[3-9]\d{9}$
  2. 文本搜索与替换
    • 替换多个空格为一个:\s+ → " "
    • 找出所有数字:\d+
  3. 日志分析
    • 匹配 IP 地址:\b\d{1,3}(\.\d{1,3}){3}\b
  4. 网页爬虫
    • 提取 HTML 标签内容:<title>(.*?)</title>

注意事项

  1. 可读性差 → 正则太复杂时,维护成本高。
    解决:用 分段注释编译模式 re.VERBOSE(Python)。
  2. 性能问题 → 复杂正则可能导致回溯爆炸。
    比如 ^(a+)+$ 匹配 “aaaaaaaaaaaaaaaaaaaa!” 时,会卡死。
  3. 不同语言差异 → 各语言的正则引擎略有不同(Python、Java、JS 都有差别)。

re 模块常用函数

Python 的正则库:

import re

re.match(pattern, string, flags=0)

作用

  • 只在字符串开头 尝试匹配。
  • 如果开头不符合规则,就直接返回 None,不会继续查找。

参数

  • pattern:正则表达式模式。
  • string:要匹配的目标字符串。
  • flags:匹配模式(忽略大小写、多行、点号匹配换行等)。

返回值

  • 匹配成功:返回 Match 对象(可以用 .group().span() 等方法取结果)。
  • 匹配失败:返回 None

示例

import retext = "Cats are smarter than dogs"m = re.match(r'Cats', text)
print(m.group())       # Cats
print(m.start(), m.end())  # (0, 4)print(re.match(r'dogs', text))  # None,因为不是开头

注意

  • 常见用途:判断字符串格式,比如是否以 "http://" 开头。
  • 如果想要在任意位置匹配,用 re.search()

re.search(pattern, string, flags=0)

作用

  • 在整个字符串中搜索 第一个匹配项
  • 只返回第一个,不会继续找。

示例

m = re.search(r'dogs', "Cats are smarter than dogs")
print(m.group())   # dogs

应用场景

  • 检查字符串中是否包含某个模式(如检测邮箱中是否包含 @)。

match vs search 的区别

line = "Cats are smarter than dogs"print(re.match(r'dogs', line))   # None(因为开头不是 dogs)
print(re.search(r'dogs', line))  # <re.Match object>(找到了 dogs)

总结:

  • match →\to 从开头匹配。
  • search →\to 搜索整个字符串,找到第一个即可。

re.findall(pattern, string, flags=0)

作用

  • 找出 所有非重叠的匹配项,以 列表形式返回

示例

s = "Simple is better than complex."
print(re.findall(r"ple", s))   # ['ple', 'ple']
print(re.findall(r"\w+", s))  # ['Simple', 'is', 'better', 'than', 'complex']

应用场景

  • 批量提取关键词、邮箱地址、手机号等。

re.finditer(pattern, string, flags=0)

作用

  • 返回一个 迭代器,里面是 Match 对象
  • findall 更灵活,因为能拿到更多信息(位置、分组等)。

示例

s = "Simple is better than complex."for m in re.finditer(r"is", s):print(m.group(), m.span())
# is (7, 9)

应用场景

  • 需要知道匹配结果的 具体位置(如文本标注、日志分析)。

re.sub(pattern, repl, string, count=0, flags=0)

作用

  • repl 替换字符串中匹配到的内容。
  • count 控制替换次数(默认替换所有)。

示例

phone = "2004-959-559 # This is Phone Number"# 去掉注释
num = re.sub(r"#.*$", "", phone)
print(num)   # 2004-959-559# 去掉非数字
num = re.sub(r"\D", "", phone)
print(num)   # 2004959559

应用场景

  • 数据清洗,比如去掉 HTML 标签、过滤特殊字符。

re.compile(pattern, flags=0)

作用

  • 把正则表达式编译成一个 正则对象,后续可重复使用,提高效率。

示例

pattern = re.compile(r"is")
print(pattern.findall("Simple is better than complex."))  # ['is']
print(pattern.findall("This is a test."))                # ['is', 'is']

优点

  • 多次使用同一个正则时,效率更高。
  • 代码更清晰:pattern.findall() 代替 re.findall(pattern, ...)

补充:Match 对象常用方法

无论是 match()search()finditer(),匹配成功时都会返回一个 Match 对象

常用方法:

  • .group() →\to 返回匹配的字符串
  • .span() →\to 返回 (起始位置, 结束位置)
  • .start() / .end() →\to 起始和结束下标
  • .groups() →\to 返回所有分组内容

例:

m = re.search(r'(\d+)-(\d+)-(\d+)', "2004-959-559")
print(m.group())   # 2004-959-559
print(m.group(1))  # 2004
print(m.group(2))  # 959
print(m.group(3))  # 559
print(m.groups())  # ('2004', '959', '559')

正则表达式修饰符(Flags)

在 Python 的 re 模块中,修饰符(flags)用来改变正则表达式的默认匹配行为。
它们通常通过两种方式使用:

  1. 在函数调用时传参

    re.findall(pattern, string, flags=re.I | re.M)
    

    多个 flags 可以用 | 连接。

  2. 在正则表达式内部使用 (?修饰符)

    re.findall("(?i)abc", "ABC")  # 忽略大小写匹配
    

re.I / re.IGNORECASE —— 忽略大小写

作用

  • 默认情况下,正则匹配是区分大小写的,比如 abc 不能匹配 ABC
  • 加上 re.I 后,匹配时不区分大小写。

示例

import retext = "Python, PYTHON, pyThOn"# 不加 re.I
print(re.findall("python", text))
# []# 加 re.I
print(re.findall("python", text, re.I))
# ['Python', 'PYTHON', 'pyThOn']

注意事项

  • 对于 Unicode 字符(比如 ä, ü, ç),忽略大小写的行为依赖 Python 的 Unicode 数据库,可能和你的预期不完全一致。
  • 例如:德语 ß 会被当作 “ss”,这在某些情况下会带来歧义。

re.M / re.MULTILINE —— 多行模式

作用

  • 默认情况下,^ 只匹配整个字符串的开头,$ 只匹配整个字符串的结尾。
  • 开启 re.M 后,^$ 会匹配每一行的开头和结尾(遇到换行符 \n 会重置)。

示例

text = """hello world
python regex
goodbye"""# 默认模式
print(re.findall("^python", text))
# []   (只检查字符串开头,没有匹配到)# 多行模式
print(re.findall("^python", text, re.M))
# ['python']   (匹配到第二行开头的 python)

注意事项

  • \A\Z 永远只匹配字符串的开头和结尾,不受 re.M 影响。
  • 所以如果你只想匹配整个文本开头结尾,而不是逐行,就该用 \A\Z

re.S / re.DOTALL —— 点号匹配换行符

作用

  • 默认情况下,. 匹配除了换行符 \n 以外的任意字符。
  • 加上 re.S 后,. 也能匹配换行符。

示例

text = """hello
world"""# 默认模式
print(re.findall("hello.*world", text))
# []   (因为 . 不会跨行,所以匹配失败)# DOTALL 模式
print(re.findall("hello.*world", text, re.S))
# ['hello\nworld']

注意事项

  • 很常见的场景是“贪婪匹配跨行”,比如抓取 HTML 文档中 <div> ... </div> 的内容时要加 re.S
  • 但要小心,这样会让 .* 跨越非常多内容,可能导致“过度匹配”(贪婪问题),通常要配合 ? 限制。

re.X / re.VERBOSE —— 忽略正则中的空格和注释

作用

  • 默认情况下,正则表达式里写的所有空格都会被认为是匹配内容的一部分。
  • 开启 re.X 后,正则里的空格会被忽略(除非用 \ 转义),并且可以在正则中写注释 #,大大提高可读性。

示例

pattern = re.compile(r"""^           # 开头\d{4}       # 年份(4位数字)-           # 中间的横杠\d{2}       # 月份(2位数字)-           # 中间的横杠\d{2}       # 日期(2位数字)$           # 结尾
""", re.X)print(pattern.match("2025-09-24"))
# <re.Match object; span=(0, 10), match='2025-09-24'>

注意事项

  • re.X 下,正则里的空格默认会被忽略,如果你真的要匹配空格,必须写成 \ 或者 [ ]
  • re.X 可以写多行注释型正则,非常适合复杂场景。
修饰符全名作用常见用途
re.IIGNORECASE忽略大小写搜索关键字不区分大小写
re.MMULTILINE^$ 匹配每行开头结尾按行匹配日志、配置文件
re.SDOTALL. 匹配包括换行符跨行匹配多行文本
re.XVERBOSE忽略正则里的空格和注释书写复杂正则时提高可读性

高级用法

贪婪(Greedy) vs 非贪婪(Non-Greedy / Lazy)

概念

  • 贪婪模式(默认行为):在满足整体匹配的前提下,*+?{m,n} 等量词会尽可能多地匹配字符。
  • 非贪婪模式:在满足整体匹配的前提下,量词会尽可能少地匹配字符。写法是 在量词后面加 ?

示例

import retext = "<python>perl>"# 贪婪:.* 会尽可能多
print(re.findall(r"<.*>", text))
# ['<python>perl>']# 非贪婪:.*? 会尽可能少
print(re.findall(r"<.*?>", text))
# ['<python>']

贪婪匹配(.\*):

  • 正则表达式中的 .* 会尽可能匹配最长的符合条件的字符串
  • <.*> 中,.* 会从第一个 < 开始,一直匹配到最后一个 > 结束
  • 所以 <python>perl> 整个字符串被匹配为一个结果

非贪婪匹配(.\*?):

  • 当在 *+?{n,m} 等量词后加上 ?,就变成了非贪婪模式
  • 非贪婪模式会尽可能匹配最短的符合条件的字符串
  • <.*?> 中,.*? 会从第一个 < 开始,匹配到第一个 > 就停止
  • 所以只匹配到 <python> 这个最短的符合条件的字符串

使用场景

  • 贪婪适合匹配“一段完整的最大范围内容”。
  • 非贪婪适合匹配“多个小片段”,比如提取 HTML 标签。

注意事项

  • 非贪婪模式依然会在整体能匹配的情况下,逐步尝试最少字符。
  • 如果后面没有限制条件,贪婪和非贪婪的结果可能一样。
贪婪模式非贪婪模式含义
**?匹配 0 次或多次
++?匹配 1 次或多次
???匹配 0 次或 1 次
{n,m}{n,m}?匹配 n 到 m 次

分组与反向引用

分组(()

  • () 可以把正则的一部分括起来,单独当作一个子模式。
  • 匹配成功后,可以通过 group(n) 取出。
m = re.match(r"(\d+)-(\d+)", "123-456")
print(m.group(1))  # 123
print(m.group(2))  # 456
print(m.groups())  # ('123', '456')

反向引用

  • 在正则表达式中,用 \1\2 等表示对前面分组的“再次引用”。
  • 常用于匹配重复的内容。
text = "hello hello world"# (\w+) 匹配一个单词,\1 要求后面再出现相同的单词
print(re.findall(r"(\w+)\s+\1", text))
# ['hello']

匹配过程:

  • 在字符串 "hello hello world" 中:
  • 第一个 hello(\w+) 捕获(分组 1 的内容为 hello
  • 中间的空格被 \s+ 匹配
  • \1 要求匹配与分组 1 相同的内容(即 hello
  • 因此整个模式匹配 "hello hello",最终返回捕获的分组内容 ['hello']

命名分组

  • 可以给分组命名,方便读取。
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2025-09")
print(m.group("year"))   # 2025
print(m.group("month"))  # 09

正则表达式 r"(?P<year>\d{4})-(?P<month>\d{2})"

  • (?P<year>\d{4}):定义一个名为 year 的分组,匹配 4 位数字(年份)
  • -:匹配横杠分隔符
  • (?P<month>\d{2}):定义一个名为 month 的分组,匹配 2 位数字(月份)
  • (?P<name>pattern) 是命名分组的语法,name 是分组名称,pattern 是该分组的匹配模式

匹配结果处理:

  • m.group("year"):通过分组名称 year 获取该分组的匹配结果(2025
  • m.group("month"):通过分组名称 month 获取该分组的匹配结果(09
  • 除了使用名称,仍然可以使用索引引用分组(m.group(1) 对应 yearm.group(2) 对应 month

断言(Lookaround)

断言是一种“零宽断言”,意思是它只检查条件,不实际消耗字符。
常见的有 前瞻(lookahead)后顾(lookbehind)

前瞻(lookahead)

  1. 肯定前瞻 (?=...)

    • 含义:当前位置 后面必须跟着
    • 不会消耗字符,只是检查条件。
    s = "Python! Python?"
    print(re.findall(r"Python(?=!)", s))
    # ['Python']  (匹配后面跟 ! 的 Python)
    
  2. 否定前瞻 (?!...)

    • 含义:当前位置 后面不能跟着
    print(re.findall(r"Python(?!\?)", s))
    # ['Python']  (匹配后面不是 ? 的 Python)
    

后顾(lookbehind)

  1. 肯定后顾 (?<=...)

    • 含义:当前位置 前面必须是
    s = "I like #Python and #Regex"
    print(re.findall(r"(?<=#)\w+", s))
    # ['Python', 'Regex']
    
  2. 否定后顾 (?<!...)

    • 含义:当前位置 前面不能是
    print(re.findall(r"(?<!#)\w+", s))
    # ['I', 'like', 'and']
    

比如我们要从 HTML 里提取 <title>...</title> 的内容:

html = "<title>My Site</title><title>Blog</title>"# 贪婪模式(错误)
print(re.findall(r"<title>.*</title>", html))
# ['<title>My Site</title><title>Blog</title>']  -> 贪婪吃掉太多# 非贪婪模式(正确)
print(re.findall(r"<title>.*?</title>", html))
# ['<title>My Site</title>', '<title>Blog</title>']# 去掉标签,只保留内容(用分组)
print(re.findall(r"<title>(.*?)</title>", html))
# ['My Site', 'Blog']

应用案例

匹配邮箱地址

基本模式

import repattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"print(re.findall(pattern, "My email is test@mail.com"))
# ['test@mail.com']

解释

  • [a-zA-Z0-9._%+-]+:邮箱用户名部分,可以包含字母、数字、点、下划线、百分号、加号、减号。
  • @:邮箱固定格式,必须有一个 @
  • [a-zA-Z0-9.-]+:邮箱域名部分,可以包含字母、数字、点、减号。
  • \.:点号要转义,表示真正的 .
  • [a-zA-Z]{2,}:顶级域名(TLD),至少两个字母,比如 .com.cn.org

注意事项

  • 这个正则能匹配大部分常见邮箱,但不是 RFC 标准,所以不一定涵盖所有情况(例如带引号的用户名)。
  • 比如 user+alias@gmail.com 能匹配,但 user@[123.123.123.123] 不行。

改进版

如果想更严格,可以这样:

pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[A-Za-z]{2,}$"

加上 ^$,确保整个字符串就是邮箱,而不是文本中的一部分。

匹配中国手机号

基本模式

pattern = r"^1[3-9]\d{9}$"print(re.match(pattern, "13812345678"))
# <re.Match object; span=(0, 11), match='13812345678'>

解释

  • ^:开头。
  • 1:中国手机号都是以 1 开头。
  • [3-9]:第二位号码范围(中国手机号第二位是 3–9 之间的数字)。
  • \d{9}:后面 9 位数字。
  • $:结尾。

总长度正好 11 位。

注意事项

  • 这个正则覆盖了常见的手机号段(13x、15x、18x、17x、19x 等)。
  • 但并不是所有 [3-9] 的组合都有效,比如 140xxxx 曾是上网卡号,现在基本停用。

改进版(更严格)

如果想匹配常见号段,可以写:

pattern = r"^1(3\d|4[5-9]|5[0-35-9]|6[5-7]|7[0-8]|8\d|9[0-35-9])\d{8}$"

这会更精准,但缺点是可读性差。

匹配所有副词(以 -ly 结尾)

基本模式

text = "He was carefully disguised but captured quickly by police."print(re.findall(r"\w+ly\b", text))
# ['carefully', 'quickly']

解释

  • \w+:匹配一个或多个字母/数字/下划线(这里主要是单词)。
  • ly:以 ly 结尾。
  • \b:单词边界,确保 ly 出现在单词末尾(避免匹配 applysomething 这种情况)。

注意事项

  • 这种方式能匹配大多数副词,但也可能误伤,比如 “silly” 其实是形容词。
  • 正则只能做“形式匹配”,没办法理解语法,所以不能百分百正确识别词性。

改进版

如果只想匹配 纯单词,而不是带符号的:

pattern = r"\b[a-zA-Z]+ly\b"

这样就避免匹配到数字或下划线。

http://www.dtcms.com/a/403642.html

相关文章:

  • Jenkins Pipeline中关于“\”的转义字符
  • 如何与AI有效沟通:描述问题及提示词技巧
  • 网站建设连接数据库我赢职场wordpress
  • TDengine 聚合函数 ELAPSED 用户手册
  • Android音频学习(二十)——高通HAL
  • C#练习题——Lambad表达式的应用
  • Polar WEB(1-20)
  • 湖州做网站公司哪家好温州市网站制作公司
  • NW973NW976美光固态闪存NW982NW987
  • 软件测试 - 接口测试(中篇)
  • 项目进不了index.php,访问public下的html文件可以进去
  • 得力D31系列M2500 M3100 ADNW激光打印机维修手册
  • 信誉好的东莞网站推广从网站验证码谈用户体验
  • Spring Boot中Bean Validation的groups属性深度解析
  • Linux进程(2)
  • C++:String类
  • 金华网站开发杭州自适应网站建设
  • ROS (无人机、机器人)与外部系统对接
  • 苏州市吴江住房和城乡建设局网站书籍网站设计
  • Pytorch工具箱2
  • 物业网站开发wordpress英文博客模板下载
  • 光影(1)
  • iOS 混淆与机器学习模型保护 在移动端保密权重与推理逻辑的实战指南(iOS 混淆、模型加密、ipa 加固)
  • Axios的快速入门
  • 网站建设品牌公司排名网页游戏4399在线游戏
  • 木渎建设局网站哪个网站可以做加工代理的
  • 培训班小程序模板如何一键套用,分享微信小程序的制作方法
  • 陕西做天然气公司网站网站如何做的有特色
  • 娱乐网站的代理怎么做WordPress 短码转换
  • Unity - Spine