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

python30-正则表达式

在Python中需要通过正则表达式对字符串进⾏匹配的时候,可以使⽤⼀个python自带的模块,名字为re。

re模块的使用:import re

一、匹配函数

1-1、re.match函数:返回匹配对象

match函数实现的是精准匹配,尝试从字符串的开头匹配模式,如果匹配成功,则返回一个匹配对象,否则返回None。

语法:

re.match(pattern, string)
  • 只在 字符串开头 尝试匹配

  • 如果开头不匹配,就直接返回 None

示例:

1-2、re.search函数:返回匹配对象

search()可以在整个字符中查找第一个匹配项,而不仅仅是从开头!返回第一个匹配到的结果!

语法:

re.search(pattern, string)
  • 整个字符串 中找 第一个匹配

  • 找到就返回 Match 对象,没有就 None

示例:

【小结】:re.match()函数 VS re.search()函数

re.match()re.search() 都是 只找第一个匹配一旦找到就停止,不会继续往下找。

如果你想要 所有匹配,就得用

  • re.findall()(直接得到 list)

  • re.finditer()(得到迭代器,能看位置等更多信息)

方法从哪里开始找找到一个后常见用途
re.match()从字符串 开头 开始匹配停止,不再继续检查开头是否符合格式(如校验手机号、日期)
re.search()从字符串 任意位置 开始找停止,不再继续找字符串中 第一次 出现的匹配

1-3、re.findall函数:直接返回 列表list

语法:

re.findall(pattern, string)
  • 找到 所有非重叠匹配的列表,直接返回 列表list

  • 如果 pattern 中有分组,只返回分组匹配的内容

示例:

1-4、re.finditer函数:返回一个 迭代器

语法:

re.finditer(pattern, string)
  • 找到所有匹配,但返回的是一个 迭代器(每个元素是 Match 对象)

  • 更适合需要匹配位置或懒加载的场景

若是你想遍历所有的匹配项,并且希望得到每个匹配项更多的信息(如:位置),可以使用re.finditer函数。

二、raw-string 格式(原始字符串)


1. 什么是 raw-string

  • Python 普通字符串中,\转义符

    • "\n" 代表换行

    • "\t" 代表制表符

  • raw-string(原始字符串)中,\ 不会被当作转义符,而是原样保留

  • 写法是在字符串前加 rR

s1 = "a\nb"
s2 = r"a\nb"print(s1)  # a 换行 b
print(s2)  # a\nb

2. 为什么正则特别需要 raw-string

正则表达式里 反斜杠 \ 本身就是语法的一部分(比如 \d, \w, \s 等),
如果不用 raw-string,就得写双反斜杠避免 Python 把它当作转义符。

例子:

import re# 不用 raw-string
pattern1 = "\\d+"   # Python 字符串先转义成 \d
print(re.findall(pattern1, "a123b"))  # ['123']# 用 raw-string
pattern2 = r"\d+"   # \d 原样传给正则引擎
print(re.findall(pattern2, "a123b"))  # ['123']

结果是一样的,但 raw-string 更直观,省得到处写 \\


3. 什么时候不能用 raw-string

  • raw-string 里最后一个字符不能是单个反斜杠

r"abc\"   # ❌ 会报错

因为 Python 语法本身需要 \ 转义引号,raw-string 也要遵守。


4. 总结

写法Python 看到的内容适合场景
"\\d+"\d+正则,但写法麻烦
r"\d+"\d+正则推荐写法

建议你记成一句话:“写正则时,前面加个 r,少写反斜杠,多活十年。” 

三、常用正则表达式常见的符号

3-1、匹配单个字符

字符功能
.匹配除换行符\n以外的任意单个字符
[ ]匹配[ ]中列举的字符[abc]: 匹配 a 或 b 或 c 中的一个字符
[^ ]匹配不是[ ]中列举的字符[^abc]:匹配 不是 a/b/c 的一个字符
\d匹配数字等价于:[0-9]
\D匹配非数字等价于:[^0-9]
\s匹配空白符(空格、制表符\t、换行\n,换页\f、回车\r 等)

等价于:[ \t\n\r\f\v]

\S匹配非空白字符[^ \t\n\r\f\v]
\w匹配单词字符(字母、数字、下划线)[a-zA-Z0-9_]
\W匹配非单词字符[^a-zA-Z0-9_]

记忆技巧

  • d → digit(数字)

  • s → space(空格)

  • w → word(单词字符)

  • 大写版本(\D \S \W)就是取反

1、.(点号)

匹配除换行符\n以外的任意单个字符

【注意】:

2、匹配一组字符:[ ]

【注意】:

【注意1】:

正则表达式 里,方括号 [ ] 不是用来表示数值范围的,而是用来表示 “字符集合”

正则里的 [] 永远是匹配单个字符

  • [abc] → 匹配 abc 中的一个字符

  • [0-9] → 匹配 0 到 9 中的一个数字字符

  • [50-99] → 不是匹配两位数,而是:

    1. 匹配 5

    2. 或匹配 0~9 之间的单个数字字符,实际等价于 [0-9],只是多写了个 5,没啥区别。

如果你要匹配 50 到 99 的数字,不能用 [50-99],而是:

5[0-9]|9[0-9]      # 两位数写法

【记住】:[ ] → 字符集合,一次只匹配一个字符!!!!

【注意2】:

] 在集合里如果不是第一个字符需要用 \] 转义,否则会被当作结束方括号。

3、排除一组字符[^...]

4、匹配一组范围:[x-z]

5、综合练习

3-2、匹配多个字符

数量词含义示例匹配效果(字符串 "aaab"
*0 次或多次a*'aaa'
+1 次或多次a+'aaa'
?0 次或 1 次a?'a'
{n}恰好 n 次a{2}'aa'
{n,}至少 n 次a{2,}'aaa'
{n,m}n 到 m 次a{1,3}'aaa'

以上这些字符也称为量词!

在正则表达式里,数量词(Quantifier)就是用来描述 某个模式重复出现多少次 的符号。

1、*和+

  • *:匹配前⼀个字符出现 0次或者多次;(>= 0
  • +:匹配前⼀个字符出现 一次或者多次。(>= 1

示例:

结果分析:

  • a* 表示:匹配 零个或多个连续的 a

  • 可以匹配空字符串(零个 a

起始位置(index)当前字符a* 匹配结果说明
0'a''aaa'从开头连续匹配了 3 个 'a',停在 index=3(字符 'b' 之前)
3'b''''b' 不是 'a',匹配 0 个 'a'(空字符串)
4结束''到了字符串末尾,匹配 0 个 'a'(空字符串)

如果不想要空匹配,可以改成 a+(至少一个 a):

import re
pattern = "a+"
print(re.findall(pattern, "aaab"))  # ['aaa']

【注意】:

.* 表示“匹配任意数量的任意字符(除了换行)

2、?(问号)

匹配前⼀个字符出现1次或者0次,即要么有1次,要么没有。

示例:

【小结】:+、*、?

3、{m, n}

匹配前⼀个字符出现从m到n次

m必须要有,但是可以是0;

n是可选的,若省略n,则{m, }匹配m到无限次。

{m, n}是贪婪匹配,尽可能多的匹配。

3-3、贪婪匹配和⾮贪婪匹配

Python⾥数量词默认是贪婪的(在少数语⾔⾥也可能是默认⾮贪婪),总是尝试匹配尽可能多的字符;⾮贪婪则相反,总是尝试匹配尽可能少的字符。

贪婪量词非贪婪量词含义
**?0 次或多次
++?1 次或多次
???0 次或 1 次
{m,n}{m,n}?m 到 n 次

记住规律:只要在量词后面加 ?,就变成非贪婪(懒惰)模式

示例:

3-4、匹配开头结尾

字符功能
^匹配字符串开头
$匹配字符串结尾

在 Python 正则里,匹配开头和结尾主要用两个锚点(Anchor):


1. ^ —— 匹配开头

  • 匹配字符串的开始位置

  • 如果开启了 re.MULTILINE,还会匹配每一行的开头

例子:

import res = "hello\nworld"print(re.findall(r"^h", s))  
# ['h']   → 只匹配整个字符串的开头print(re.findall(r"^w", s, re.MULTILINE))  
# ['w']   → 匹配第二行的开头,因为 MULTILINE 模式

【注意】:


2. $ —— 匹配结尾

  • 匹配字符串的结束位置

  • 如果开启了 re.MULTILINE,还会匹配每一行的结尾

例子:

print(re.findall(r"d$", s))
# ['d']  → 只匹配整个字符串的结尾print(re.findall(r"o$", s, re.MULTILINE))
# ['o']  → 匹配第一行的结尾

3. 常用组合

模式含义示例
^abc"abc" 开头"abc123" ✅, "1abc"
abc$"abc" 结尾"123abc" ✅, "abc1"
^$匹配空字符串或空行MULTILINE 模式下可匹配空行

4、综合应用:使用^...$实现完全匹配

四、替换函数

re.sub()re.subn(),它们都是 Python 正则模块 re 里用来做替换的函数。

4-1、re.sub()函数

语法:

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

将string 中所有匹配 pattern 的部分用repl替换掉。

  • pattern:正则表达式

  • repl:替换的内容(字符串,或函数)

  • string:原文本

  • count:替换次数,默认 0 表示替换所有

  • flags:正则匹配模式(re.IGNORECASE 等)

示例:

4-2、re.subn()函数

语法:

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

sub 一样,也是替换,但它多返回一个替换次数

返回值一个 元组 (new_string, number_of_subs)

  • new_string:替换后的字符串

  • number_of_subs实际替换的次数。

五、好用的正则函数可视化的方法

推荐使用检验网站:https://regexper.com/

【分析】:

这个正则是一个 邮箱地址的匹配模式

(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)

1. 外层括号 ( … )

  • 把整个邮箱模式放进一个捕获分组里(其实这里不分组也可以,只是会让 group(1) 直接得到整个邮箱地址)。


2. ^

  • 锚点:匹配字符串的开头位置。

  • 这样保证邮箱必须从开头就符合这个模式,而不是出现在中间。


3. [a-zA-Z0-9_.+-]+

  • 字符集:匹配邮箱用户名(@ 前面)的合法字符。

    • a-z → 小写字母

    • A-Z → 大写字母

    • 0-9 → 数字

    • _ . + - → 下划线、点、加号、减号

  • + → 匹配 1 次或多次(不能为空)。


4. @

  • 字面量 @ 符号,分隔用户名和域名。


5. [a-zA-Z0-9-]+

  • 匹配域名第一部分(@ 后、. 前)

    • 允许字母、数字和 -

    • + → 至少一个字符。


6. \.

  • \. 匹配一个 字面量点 .(因为普通的 . 在正则里是“任意字符”,要用反斜杠转义才能表示“点”)。


7. [a-zA-Z0-9-.]+

  • 匹配域名后缀部分,比如:

    • com

    • co.uk

    • xn--fiqs8s(国际化域名 punycode)

  • 包含:

    • 字母 a-zA-Z

    • 数字 0-9

    • .

    • 减号 -

  • + → 至少一个字符。


8. $

  • 锚点:匹配字符串的结尾位置。

  • 确保整个字符串正好是邮箱,而不是包含邮箱的长文本。


9. 总结匹配规则

这个正则匹配的邮箱格式类似:

用户名@域名.后缀

示例匹配成功:

test.email+alex@leetcode.com
user_name@my-domain.co.uk

示例匹配失败:

test@.com       # 域名第一部分为空
test@domain     # 缺少点和后缀

六、分组(...)


1. 分组是什么

( ... ) 就像数学里的括号,把里面的正则看作一个整体

  • 它不会像字符集 [ ... ] 那样只是“列出单个可选字符”,而是把一个完整的子模式包起来。

  • 这样你可以:

    1. 整体重复(数量词作用在整个括号上)

    2. 整体作为一个单位处理

    3. 捕获里面的内容(匹配结果可以取出来)


2. 整体重复的例子

假设你想匹配:

ab abab ababab

不能直接用 ab+,因为 + 只会让 b 重复,而不是 ab 整体重复。
你需要:

(ab)+

解释:

  • (ab) → 把 ab 作为一个整体

  • + → 重复这个整体一次或多次

匹配效果:

(ab)+  → "ab", "abab", "ababab"

3. 捕获分组

分组会把匹配到的内容存起来,方便后续使用。
例子:

import re
m = re.match(r"(ab)+(\d+)", "abab123")
print(m.group(1))  # ab
print(m.group(2))  # 123

说明:

  • 第一个 (ab)+ 里的 ab 是捕获组 1

  • 第二个 (\d+) 是捕获组 2

  • 你可以.group(n) 取出对应的匹配内容


4. 不能放进字符集 [ ... ]

方括号 [ ... ] 是“字符集合”,它只能写单个字符或字符范围,不能在里面放一个完整的分组:

[ (ab) ]   ❌  会被当作匹配字符 '('、'a'、'b'、')'
(ab)       ✅  这是分组,能当一个整体用

5. 小总结

功能举例说明
作为整体(ab)++ 作用在整个 ab
捕获内容(ab)(\d+)可以用 .group(1).group(2) 取出
提高可读性`(?:abcdef)`
不能在字符集里[()]只能匹配 () 这样的单个字符

七、Alternation(交替匹配)|


1. 基本概念

  • | 在正则中是**“或”**运算符(OR)。

  • A|B 会匹配 AB 任意一个能匹配成功的模式。

例子:

apple|orange

匹配 "apple""orange"


2. 匹配顺序(优先级)

  • 从左到右匹配,第一个能匹配的就用它,不会去尝试右边的

  • 这叫左侧优先(leftmost precedence)。

例子:

cat|cater

匹配 "cat" 时就会停,不会去匹配 "cater",因为 "cat" 在左边先匹配成功了。


3. 多个模式可连续连接

  • 你可以把很多模式用 | 串起来,比如:

dog|cat|bird

匹配 "dog""cat""bird"


4. 用括号分组以避免歧义

  • 如果直接写:

apple|orange juice

它匹配的是 "apple""orange juice",并不会匹配 "apple juice"

  • 如果你想让 "juice" 在两种水果后面都适用,需要用分组:

(apple|orange) juice

匹配 "apple juice""orange juice"

【注意】:

| 左右是整个模式,所以哪怕有空格,它也会把空格算进匹配里。


5. 例子解析

  • apple|orange"apple""orange"

  • (apple|orange) juice"apple juice""orange juice"

  • w(ei|ie)rd"weird""wierd"(括号内是两个可选的字母组合)


要点总结

写法含义注意事项
A|B逻辑 OR(交替),findall 不会单独返回捕获值左侧优先
(A|B)分组交替,捕获分组,findall 会返回匹配的那一部分括号能避免歧义
A|B|C多个交替可以无限串联

八、re.compile 函数

在 Python 的 re 模块中,re.compile() 函数用于将正则表达式模式编译为一个正则表达式对象RegexObject),以便后续重复使用

这种编译操作可以提高正则表达式的匹配效率,尤其适合需要多次使用同一模式的场景。

1、基本语法

re.compile(pattern, flags=0)
  • pattern:必填,字符串类型的正则表达式模式。
  • flags可选,用于修改正则表达式行为的标志(如忽略大小写、多行模式等)。

主要作用

  1. 提高效率
    当同一个个正则表达式需要被多次使用时(例如在循环中匹配),提前用 re.compile() 编译可以避免重复解析模式,从而提升性能。

  2. 复用正则对象
    编译后返回的正则对象可以直接调用 match()search()findall() 等方法,语法更简洁。

2、使用示例

示例 1:基础用法
import re# 编译正则表达式(匹配邮箱格式)
pattern = re.compile(r'\w+@\w+\.\w+')# 使用编译后的对象进行匹配
text1 = "我的邮箱是test@example.com"
text2 = "联系我:abc123@gmail.com"# 查找匹配项
print(pattern.findall(text1))  # 输出:['test@example.com']
print(pattern.findall(text2))  # 输出:['abc123@gmail.com']
示例 2:使用标志(flags)

常用标志包括:

  • re.IGNORECASEre.I):忽略大小写匹配。
  • re.MULTILINEre.M):多行模式,使 ^ 和 $ 匹配每行的开头和结尾。
import re# 忽略大小写匹配 "hello"
pattern = re.compile(r'hello', re.IGNORECASE)print(pattern.findall("Hello World"))  # 输出:['Hello']
print(pattern.findall("HELLO Python"))  # 输出:['HELLO']

与直接使用函数的区别

不编译模式时,也可以直接使用 re.findall(pattern, text) 等函数,但本质上这些函数内部会隐式编译模式。因此:

  • 单次使用:两种方式效率差异不大
  • 多次使用:re.compile() 编译后复用的方式更高效。

总结

    re.compile() 是优化正则表达式性能的重要工具,尤其适合在循环或批量处理中重复使用同一模式的场景。编译后的正则对象提供了与 re 模块函数对应的方法(如 match()search()),使用起来更加灵活。

好的,我们来翻译并讲解这一段 “The Backslash Plague” 的意思。


九、反斜杠灾难(The Backslash Plague)

  • 反斜杠 \ 用来表示特殊形式(special forms),或者让特殊字符失去它原本的特殊含义(转义)。

  • 因此,如果要在正则表达式中匹配一个真正的反斜杠,你必须写成 '\\\\' 作为正则表达式字符串。

  • 那么,我们能不能简化这种写法呢?


这个“灾难”是因为反斜杠在 Python 字符串正则表达式 里都被当作转义符号:

  1. Python 字符串解析阶段

    • 在普通字符串 "\\n" 中,\\ 会被 Python 解析成一个反斜杠字符 \,而 \n 则会被解析成换行符。

    • 所以如果你想在字符串里写一个“正则表达式里的反斜杠”,就得先把它在 Python 里转义\\

  2. 正则表达式解析阶段

    • 在正则里,反斜杠本身也有特殊含义,比如 \d 是数字、\w 是单词字符。

    • 如果你想匹配反斜杠本身,还得在正则层面再转义一次,所以是 \\(正则层面)。

  3. 叠加效果

    • 在 Python 普通字符串里写正则匹配反斜杠,要写成:

      "\\\\"
      
      • 前两层 \\ → Python 转义成 \

      • 后两层 \\ → 正则转义成 “匹配一个反斜杠”


9-1、如何简化?

原始字符串 r"..."

pattern = r"\\"

这样:

  • Python 不会解析 \,直接把 \\ 交给正则引擎

  • 只需要一层 \\ 表示“匹配一个反斜杠”


对比表

想匹配内容普通字符串写法原始字符串写法
一个反斜杠 \"\\\\"r"\\"
数字字符 \d"\\d"r"\d"
点号 ."\\."r"\."

十、捕获组(...)

用(...)括起来,希望匹配结果中提取出特定的部分。

10-1、不捕获组 VS 分组

所有的捕获组都是分组,但并不是所有的分组都是捕获组


1. 分组(Grouping)

括号 () 在正则里的第一个作用就是把多个模式当作一个整体,方便:

  • 改变运算优先级

  • 给整个模式应用数量词(*, +, {m,n} 等)

例子:

import re
print(re.findall(r'(ab)+', "abababx"))  # ['ab']

这里 (ab)+ 表示“ab 这个整体重复一次或多次”。


2. 捕获组(Capturing Group)

括号的第二个作用是捕获匹配到的内容,以便之后使用:

  • 通过 re.findall() 返回分组内容

  • re.search() 里用 .group(n) 获取

  • re.sub() 中通过 \1, \2 引用

例子:

示例字符串

text = "John Smith, Alice Brown"

目标:匹配 "名字 姓氏" 的模式,并把名字和姓氏分别作为分组。


1️⃣ 通过 re.findall() 返回分组内容

findall() 如果正则里有捕获组,会只返回组的内容(如果有多个组,就返回元组)。

import repattern = r"(\w+)\s+(\w+)"  # 第1组=名字, 第2组=姓氏
matches = re.findall(pattern, text)
print(matches)

输出:

[('John', 'Smith'), ('Alice', 'Brown')]

每个元组 (group1, group2) 对应一次匹配的两个分组内容。


2️⃣ re.search() 里用 .group(n) 获取

search() 只找第一个匹配.group(0) 是整个匹配.group(1) 是第1组,依此类推。

m = re.search(pattern, text)
print(m.group(0))  # 整个匹配:John Smith
print(m.group(1))  # 第1组:John
print(m.group(2))  # 第2组:Smith

输出:

John Smith
John
Smith

3️⃣ re.sub() 中通过 \1, \2 引用

sub() 的替换字符串里,可以用 \1, \2 引用捕获组的内容。

result = re.sub(pattern, r"\2, \1", text)  
print(result)

输出:

Smith, John, Brown, Alice

这里 \2 是姓氏,\1 是名字,所以替换成了 “姓, 名” 的形式。


总结表格

用法获取方式返回值特点
findall()返回列表有分组时返回元组列表
search().group(n)数字 n只获取第一个匹配,0 是全部
sub() 替换字符串 \n反斜杠加组号在替换时插入对应组内容


10-2. 非捕获组(Non-Capturing Group)

如果只想分组不想捕获,可以用 (?:...)。这样括号只负责逻辑分组,不会把内容保存到组里:

import re
print(re.findall(r"(?:ab)+", "abababx"))  # ['ababab']

不会产生 .group(1) 等捕获内容。

10-3、命名捕获组(?P<name>)

使用(?P<name>)给捕获组命名,然后在re.search()中,通过.group('name')引用

在re.sub()替换字符串里,可以用 \g<name> 来引用命名组(比数字引用更直观)。


总结

写法是否分组是否捕获常用用途
( ... )捕获内容、数量词作用范围
(?: ... )仅用于逻辑分组
(?P<name>...)✅(命名)捕获并用名字引用

http://www.lryc.cn/news/621705.html

相关文章:

  • Kotlin作用域函数全解:run/with/apply/let/also与this/it的魔法对决
  • shell脚本实现sha256sum校验并拷贝校验通过的文件
  • 【Spring框架】SpringIOC
  • 代码随想录二刷之“字符串”~GO
  • 状态管理中应用进程和宿主进程的概念及相互关系
  • 初识CNN02——认识CNN2
  • Jeecg后端经验汇总
  • redis-sentinel基础概念及部署
  • JVM执行引擎深入理解
  • 异步开发:协程、线程、Unitask
  • 关于C++的#include的超超超详细讲解
  • LCR 076. 数组中的第 K 个最大元素
  • IStoreOS(OpenWrt)开启IPV6
  • 【已解决】在Spring Boot工程中,若未识别到resources/db文件夹下的SQL文件
  • 10--C++模板参数与特化详解
  • Linux Namespace隔离实战:dd/mkfs/mount/unshare构建终极沙箱
  • 基于CodeBuddy的2D游戏开发实践:炫酷大便超人核心机制解析
  • 云手机存储和本地存储的区别
  • Ant-Design AUpload如何显示缩略图;自定义哪些类型的数据可以使用img预览
  • 用3D打印重新定义骑行-中科米堆CASAIM自行车座椅个性化设计
  • Spring Ai 如何配置以及如何搭建
  • Cursor CLI 技术解析:免费调用 GPT-5 的命令行方案
  • Flink的状态管理
  • 项目篇------------网页五子棋(知识预备)
  • GPT 解码策略全解析:从 Beam Search 到 Top-p 采样
  • spring ai-openai-vl模型应用qwen-vl\gpt-文字识别-java
  • 自学大语言模型之Transformer的Tokenizer
  • 用GPT解释“GPT-5”是什么,有什么优势
  • Spring IOC容器在Web环境中的启动奥秘:深入源码解析
  • Grafana 与 InfluxDB 可视化深度集成(一)