Python
基础语法
01概念
02安装
03变量
04字符串
05数
06常量与注释
07列表
08元组
09if语句
10字典
11集合
12复合数据类型对比
13推导式
14用户输入
15while循环
16函数
17类
18面向对象编程
19文件操作
20异常处理
21日期和时间
22魔术方法
23内置函数
24线程
25并发&并行
26正则表达式
27迭代器
28装饰器
29生成器
30上下文管理器
31函数式编程
32闭包
33解包
34工具库
35连接关系型数据库
36虚拟环境
37异步编程
网络爬虫
01urllib库[了解]
02requests库
03数据交换格式
04解析库
05lxml
06Beautiful Soup
07Xpath语法
08动态网页的处理
-
+
首页
26正则表达式
Python 的正则表达式(Regular Expression)是通过标准库re模块实现的文本处理工具,用于匹配、搜索、替换和提取符合特定模式的字符串。它继承了正则表达式的通用语法,同时结合 Python 特性提供了灵活的 API,广泛应用于数据验证、日志分析、文本解析等场景。 ## 概述 正则表达式是**描述字符串模式的规则**,通过特殊符号组合定义 “符合某种特征的字符串集合”。例如,r'\d{3}-\d{4}'定义了 “3 位数字 - 4 位数字” 的模式(如123-4567)。 在 Python 中,正则表达式通常用**原始字符串(r"")** 表示,避免反斜杠\的二次转义(如r'\n'表示正则中的\n,而'\n'在 Python 中是换行符)。 re模块提供了所有正则操作的函数,核心功能包括: - 匹配与搜索(match()、search()、findall()等); - 替换(sub()、subn()); - 分割(split()); - 预编译(compile())。 使用前需导入模块:import re。 ## 基本语法 Python 支持正则表达式的通用语法,核心元素包括**原子(单个字符)**、**量词(匹配次数)**、**边界符**、**分组**等。 **1. 原子:匹配单个字符** 原子是正则的最小单位,用于匹配单个字符: | 类型 | 语法 / 符号 | 描述 | 示例 | | ---------- | --------------- | ------------------------------------------ | -------------------------------- | | 普通字符 | a、1、@等 | 匹配自身(除元字符外) | r'abc'匹配"abc" | | 元字符 | . | 匹配任意单个字符(除\n,re.S模式下包含\n) | r'a.b'匹配"aab"、"a@b" | | 数字 | \d | 匹配任意数字(等价于[0-9]) | r'\d\d'匹配"12"、"05" | | 非数字 | \D | 匹配任意非数字(等价于\[^0-9]) | r'\D\D'匹配"ab"、"@#" | | 单词字符 | \w | 匹配字母、数字、下划线([a-zA-Z0-9_]) | r'\w+'匹配"user123" | | 非单词字符 | \W | 匹配非单词字符(\[^a-zA-Z0-9_]) | r'\W'匹配"@"、" "(空格) | | 空白字符 | \s | 匹配空格、制表符\t、换行符\n等 | r'a\sb'匹配"a b"、"a\tb" | | 非空白字符 | \S | 匹配非空白字符 | r'a\Sb'匹配"a1b"、"a@b" | | 字符集 | [abc] | 匹配a、b、c中的任意一个 | r'[abc]'匹配"a"、"b" | | 字符范围 | [a-z]、[0-9A-Z] | 匹配范围内的任意字符 | r'[a-z]'匹配"x"、"y" | | 否定字符集 | [^abc] | 匹配除a、b、c外的任意字符(取反) | r'\[^abc]'匹配"d"、"1" | | 转义字符 | \.、\*等 | 匹配元字符本身(取消特殊含义) | r'a\\.b'匹配"a.b"(不匹配"aab") | **2. 量词:控制原子的匹配次数** 量词用于指定 “前面的原子或分组” 的匹配次数,默认是**贪婪模式**(尽可能匹配最长字符串): | 量词 | 描述 | 示例 | | ------------------ | ---------------------------------- | ------------------------------- | | * | 匹配 0 次或多次(贪婪) | r'a*'匹配""、"a"、"aaa" | | + | 匹配 1 次或多次(贪婪) | r'a+'匹配"a"、"aaa"(不匹配"") | | ? | 匹配 0 次或 1 次(贪婪) | r'a?'匹配""、"a" | | {n} | 匹配恰好 n 次 | r'a{3}'匹配"aaa" | | {n,} | 匹配至少 n 次 | r'a{2,}'匹配"aa"、"aaa" | | {n,m} | 匹配至少 n 次,至多 m 次 | r'a{2,4}'匹配"aa"、"aaaa" | | *?、+?、??、{n,m}? | 非贪婪模式(尽可能匹配最短字符串) | r'a+?'匹配"a"(而非"aaa") | **3. 边界匹配符:限定位置** 匹配字符串的**位置**(而非字符),如开头、结尾等: | 符号 | 描述 | 示例 | | ---- | ---------------------------------------- | ----------------------------------------------- | | ^ | 匹配字符串开头(re.M模式下匹配每行开头) | r'^abc'匹配"abc123"(不匹配"xabc") | | $ | 匹配字符串结尾(re.M模式下匹配每行结尾) | r'abc$'匹配"123abc"(不匹配"abcx") | | \b | 匹配单词边界(单词与非单词字符的交界) | r'\bcat\b'匹配"cat"、"cat."(不匹配"category") | | \B | 匹配非单词边界(单词内部) | r'\Bcat\B'匹配"category"(不匹配"cat") | **4. 分组与引用:处理子模式** 用()将原子组合为 “子模式”,支持捕获、引用和逻辑选择: - 普通分组(捕获组) (pattern):匹配子模式并捕获结果,可通过编号引用(\1、\2...)。 示例:r'(\d{3})-(\d{4})'匹配"123-4567"时,\1引用"123",\2引用"4567"。 - 命名分组 (?P\<name>pattern):为分组命名,可通过名称name或编号引用(\g\<name>、\g<1>)。 示例:r'(?P\<area>\d{3})-(?P\<num>\d{4})'中,\g\<area>引用区号,\g\<num>引用号码。 - 非捕获分组 (?:pattern):仅分组,不捕获结果(节省内存)。 示例:r'(?:ab)+'匹配"abab"(ab重复 2 次),但不捕获"ab"。 - 逻辑选择 |:匹配多个子模式中的任意一个(“或” 逻辑)。 示例:r'cat|dog'匹配"cat"或"dog";r'(a|b)c'匹配"ac"或"bc"。 **5. 预查(零宽断言):匹配位置的前后条件** **零宽**:不匹配具体字符,只匹配一个 “位置”(类似 ^ 匹配开头、$ 匹配结尾的位置),匹配后不会 “消耗” 字符串中的任何字符(即后续匹配仍从当前位置开始)。 **断言**:判断当前位置的**前后内容**是否满足指定条件(“有什么” 或 “没什么”),满足则匹配这个位置,不满足则不匹配。 预查匹配 “位置” 而非字符,用于判断某个位置的前后是否符合特定模式: | 类型 | 语法 | 核心作用(判断条件) | 通俗理解 | | -------------------- | -------- | --------------------------------------- | ----------------------------- | | 正向先行断言 | (?=...) | 当前位置**后面**必须有符合 ... 的内容 | “后面得有 XX,才匹配这位置” | | 负向先行断言 | (?!...) | 当前位置**后面**必须没有符合 ... 的内容 | “后面不能有 XX,才匹配这位置” | | 正向后行断言(后发) | (?<=...) | 当前位置**前面**必须有符合 ... 的内容 | “前面得有 XX,才匹配这位置” | | 负向后行断言(后发) | (?<!...) | 当前位置**前面**必须没有符合 ... 的内容 | “前面不能有 XX,才匹配这位置” | 例如:验证密码长度至少 6 位,且**必须包含至少 1 个数字**。 ```python import re # 正则表达式:^ 开头 → (?=.*\d) 断言后面有数字 → .{6,} 匹配至少6个任意字符 → $ 结尾 pattern = r'^(?=.*\d).{6,}$' # 测试案例 test_passwords = [ "abc123", # 含数字,长度6 → 匹配 "abcdef", # 纯字母,无数字 → 不匹配 "12345", # 有数字,但长度5 → 不匹配 "a1b2c3d4" # 含数字,长度8 → 匹配 ] for pwd in test_passwords: if re.fullmatch(pattern, pwd): print(f" 有效密码:{pwd}") else: print(f" 无效密码:{pwd}") ``` 输出结果: ```python 有效密码:abc123 无效密码:abcdef 无效密码:12345 有效密码:a1b2c3d4 ``` 讲解: ```txt ^:锚定字符串开头(确保从第一个字符开始判断) (?=.*\d):正向先行断言的核心: - .*:匹配 “任意字符(除换行)” 任意次(表示 “当前位置到字符串末尾之间可以有任意内容”)。 - \d:匹配一个数字。 - 整体意思:**“从当前位置(字符串开头)往后看,必须能找到至少一个数字”**(无论数字在第几位,只要存在)。 .{6,}:匹配至少 6 个任意字符(满足长度要求)。 $:锚定字符串结尾(确保整个字符串都被验证)。 ``` ## 核心函数 re模块提供了一系列函数处理正则匹配,按功能可分为**匹配搜索**、**替换**、**分割**和**预编译**四大类。 ### 匹配与搜索函数 用于查找字符串中符合模式的内容。 **re.match(pattern, string, flags=0)** **功能**:从字符串**开头**匹配模式,若开头不匹配则返回None。 **返回值**:匹配对象(Match)或None。 ```python import re result = re.match(r'\d+', '123abc') print(type(result)) print(result.group()) # '123'(匹配开头的数字) print(re.match(r'\d+', 'abc123')) # None(开头不是数字) ``` 输出结果: ```python <class 're.Match'> 123 None ``` **re.search(pattern, string, flags=0)** **功能**:在整个字符串中搜索**第一个**符合模式的子串(不限制开头)。 **返回值**:匹配对象(Match)或None。 ```python import re result = re.search(r'\d+', 'abc3456def123456') print(result.group()) # '123'(第一个数字) ``` 输出结果: ```python 3456 ``` **re.fullmatch(pattern, string, flags=0)** **功能**:**要求整个字符串从开头到结尾都必须符合正则模式**,不允许有任何多余的字符(即 “部分匹配” 无效)。 返回值: - 若**整个字符串完全匹配**模式,返回Match对象(包含匹配详情); - 若**有任何部分不匹配**(包括字符串长度不符、存在多余字符等),返回None。 注意:re.fullmatch()的关键是 “完全”—— 正则表达式必须**覆盖整个字符串**,等效于在正则模式前后自动隐含了^(开头)和$(结尾)。 ```python import re pattern = r'\d{3}' # 匹配3个数字 test_strings = ['123', '1234', 'a123', '123a', '12'] print("re.fullmatch结果:") for s in test_strings: result = re.fullmatch(pattern, s) print(f"'{s}' → {'匹配' if result else '不匹配'}") print("\nre.match结果:") for s in test_strings: result = re.match(pattern, s) print(f"'{s}' → {'匹配开头' if result else '不匹配开头'}") print("\nre.search结果:") for s in test_strings: result = re.search(pattern, s) print(f"'{s}' → {'包含子串' if result else '不包含子串'}") ``` 输出结果: ```python re.fullmatch结果: '123' → 匹配 '1234' → 不匹配 'a123' → 不匹配 '123a' → 不匹配 '12' → 不匹配 re.match结果: '123' → 匹配开头 '1234' → 匹配开头 'a123' → 不匹配开头 '123a' → 匹配开头 '12' → 不匹配开头 re.search结果: '123' → 包含子串 '1234' → 包含子串 'a123' → 包含子串 '123a' → 包含子串 '12' → 不包含子串 ``` **re.findall(pattern, string, flags=0)** **功能**:查找字符串中**所有**符合模式的子串,返回列表。 **返回值**:字符串列表(若有分组,返回分组内容的元组列表)。 ```python import re # 无分组:返回所有匹配的子串 print(re.findall(r'\d+', 'abc123def456')) # ['123', '456'] # 有分组:返回分组内容的元组 print(re.findall(r'(\d+)-(\d+)', '123-456,789-012,999-1')) # [('123', '456'), ('789', '012')] ``` 输出结果: ```python ['123', '456'] [('123', '456'), ('789', '012'), ('999', '1')] ``` **re.finditer(pattern, string, flags=0)** **功能**:查找所有符合模式的子串,返回**匹配对象的迭代器**(适合大量结果,节省内存)。 **返回值**:Match对象的迭代器。 ```python import re for match in re.finditer(r'\d+', 'abc123def456g101'): print(match.group()) # 依次输出'123'、'456' ``` 输出结果: ```python 123 456 101 ``` ### 替换函数 用于替换字符串中符合模式的子串。 **re.sub(pattern, repl, string, count=0, flags=0)** **功能**:用repl替换字符串中所有符合pattern的子串。 参数: - repl:替换字符串(可包含\1、\g<name>等引用分组); - count:最大替换次数(0 表示全部)。 返回值:替换后的新字符串。 ```python import re # 替换所有数字为'*' print(re.sub(r'\d+', '*', 'abc123def456')) # 'abc*def*' # 引用分组:交换两个数字 print(re.sub(r'(\d+)-(\d+)', r'\2-\1', '123-456')) # '456-123' # 命名分组引用 print(re.sub(r'(?P<area>\d{3})-(?P<num>\d{4})', r'\g<num>-\g<area>', '010-1234')) # '1234-010' ``` 输出结果: ```python abc*def* 456-123 1234-010 ``` **re.subn(pattern, repl, string, count=0, flags=0)** **功能**:与sub()类似,但返回**元组(新字符串, 替换次数)**。 ```python import re print(re.subn(r'\d+', '*', 'abc123def456')) # ('abc*def*', 2) ``` 输出结果: ```python ('abc*def*', 2) ``` ### 分割函数 **re.split(pattern, string, maxsplit=0, flags=0)** **功能**:按符合模式的子串分割字符串,返回列表。 参数:maxsplit:最大分割次数(0 表示全部分割)。 ```python import re # 按逗号或空格分割 print(re.split(r'[, ]', 'a,b c,d')) # ['a', 'b', 'c', 'd'] # 限制分割次数 print(re.split(r'[, ]', 'a,b c,d,asd,g', maxsplit=2)) # ['a', 'b', 'c,d'] ``` 输出结果: ```python ['a', 'b', 'c', 'd'] ['a', 'b', 'c,d,asd,g'] ``` ### 预编译函数 **re.compile(pattern, flags=0)** **功能**:将正则表达式编译为RegexObject对象,可重复使用(提高效率,尤其多次调用时)。 **优势**:预编译后,正则的语法检查仅执行一次,减少重复开销。 ```python import re # 预编译正则 pattern = re.compile(r'\d+') # 重复使用 print(pattern.findall('abc123')) # ['123'] print(pattern.sub('*', 'def456')) # 'def*' ``` 输出结果: ```python ['123'] def* ``` ## 匹配对象(Match)的方法 match()、search()、finditer()返回的Match对象包含匹配的详细信息,核心方法如下: | 方法 / 属性 | 描述 | 示例 | | ------------ | ---------------------------------------------- | ------------------------------------------------------------ | | group(num=0) | 返回第num个分组的匹配内容(num=0返回整个匹配) | m = re.match(r'(\d+)-(\d+)', '123-456') → m.group(1) → '123' | | groups() | 返回所有分组匹配内容的元组 | 上述m.groups() → ('123', '456') | | groupdict() | 返回命名分组的字典(键为组名,值为内容) | m = re.match(r'(?P<x>\d+)-(?P<y>\d+)', '123-456') → m.groupdict() → {'x': '123', 'y': '456'} | | start(num=0) | 返回第num个分组匹配的起始索引 | 上述m.start(1) → 0('123' 从索引 0 开始) | | end(num=0) | 返回第num个分组匹配的结束索引(不包含) | 上述m.end(1) → 3('123' 到索引 3 结束) | | span(num=0) | 返回(start, end)元组 | 上述m.span(1) → (0, 3) | ## 模式修饰符(flags) 修饰符用于修改正则的匹配行为,可作为函数参数或内联标记((?flag))使用: | 修饰符 | 缩写 | 描述 | 内联标记 | | ---------- | ---- | ------------------------------------------------ | -------- | | IGNORECASE | I | 忽略大小写匹配 | (?i) | | MULTILINE | M | 多行模式:^匹配每行开头,$匹配每行结尾 | (?m) | | DOTALL | S | 单行模式:.匹配包括\n在内的任意字符 | (?s) | | VERBOSE | X | 忽略正则中的空格和注释(#开头),提高可读性 | (?x) | | UNICODE | U | 使\w、\d等匹配 Unicode 字符(Python 3 默认启用) | - | ```python import re # 忽略大小写(参数形式) print(re.match(r'abc', 'ABC', re.IGNORECASE).group()) # 匹配成功 # 多行模式(内联标记) text = 'line1\nline2\nline3' print(re.findall(r'^line', text, re.M)) # ['line1', 'line2', 'line3'](匹配每行开头) # VERBOSE模式(多行注释) pattern = re.compile(r''' \d{3} # 3位数字 - # 连字符 \d{4} # 4位数字 ''', re.VERBOSE) print(pattern.match('123-4567')) # 匹配成功 print(pattern.match('123-4567').group()) # 匹配成功 ``` 输出结果: ```python ABC ['line', 'line', 'line'] <re.Match object; span=(0, 8), match='123-4567'> 123-4567 ``` ## 应用场景与示例 **1.验证邮箱** ```python import re # 忽略大小写(参数形式) def is_valid_email(email): pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(pattern, email)) print(is_valid_email('user@example.com')) print(is_valid_email('invalid-email')) ``` 输出结果: ```python True False ``` **2.验证中国大陆手机号** ```python import re # 忽略大小写(参数形式) def is_valid_phone(phone): pattern = r'^1[3-9]\d{9}$' # 1开头,第2位3-9,后接9位数字 return bool(re.match(pattern, phone)) print(is_valid_phone('13812345678')) print(is_valid_phone('12345678901')) print(is_valid_phone('153456789011')) ``` 输出结果: ```python True False False ``` **3.提取日志中的时间戳** ```python import re log = '2023-10-05 14:30:25 [INFO] User login\n2023-10-05 14:31:00 [ERROR] Connection failed' # 匹配YYYY-MM-DD HH:MM:SS格式 pattern = r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' print(re.findall(pattern, log)) ``` 输出结果: ```python ['2023-10-05 14:30:25', '2023-10-05 14:31:00'] ``` **4.提取 HTML 中的图片 URL** ```python import re html = '<img src="https://img1.com/pic.jpg" alt="pic1"><img src="https://img2.com/img.png">' # 提取src属性值(双引号内的内容) pattern = r'src="([^"]+)"' print(re.findall(pattern, html)) ``` 输出结果: ```python ['https://img1.com/pic.jpg', 'https://img2.com/img.png'] ``` **5.手机号脱敏(中间 4 位替换为 *)** ```python import re phone = '13812345678' print(re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', phone)) ``` 输出结果: ```python 138****5678 ``` **6.日期格式转换(MM/DD/YYYY → YYYY-MM-DD)** ```python import re date = '10/05/2023' print(re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\1-\2', date)) ``` 输出结果: ```python 2023-10-05 ``` **7.匹配 “包含字母和数字,但不包含特殊字符” 的字符串** ```python # 正向预查确保包含字母和数字,否定字符集排除特殊字符 import re # 正向预查确保包含字母和数字,否定字符集排除特殊字符 pattern = r'^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z0-9]+$' print(re.match(pattern, 'abc123')) # 匹配(有字母和数字,无特殊字符) print(re.match(pattern, 'abc123').group()) # 匹配(有字母和数字,无特殊字符) print(re.match(pattern, 'abc')) # None(无数字) print(re.match(pattern, '123')) # None(无字母) print(re.match(pattern, 'abc@123')) # None(有特殊字符@) ``` 输出结果: ```python <re.Match object; span=(0, 6), match='abc123'> abc123 None None None ``` ## 注意事项 **预编译正则**:多次使用的正则用re.compile()预编译,减少重复解析开销。 **避免过度回溯**:复杂嵌套量词(如(a+)+)可能导致 NFA 引擎效率低下,用具体范围替代(如a{1,10})。 **优先使用finditer()**:处理大量匹配结果时,迭代器比列表(findall())更省内存。 **限制匹配范围**:用^、$或具体前缀后缀缩小匹配范围(如r'^error:'仅匹配以error:开头的行)。 **混淆match()与search()**:match()仅匹配开头,search()搜索整个字符串,需根据场景选择。 **转义问题**:忘记用原始字符串r""导致\d被解析为 Python 转义符(如'\d'在 Python 中是'd',需用r'\d')。 **贪婪模式的意外匹配**:默认贪婪模式可能匹配过宽(如r'<.\*>'匹配\<div>text\</div>会返回整个字符串,应改用非贪婪r'<.*?>')。 **滥用正则解析结构化文本**:HTML、XML、JSON 等有嵌套结构的文本,优先用专用解析器(如BeautifulSoup),正则难以处理嵌套逻辑。
毛林
2025年9月7日 11:45
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码