Python 教程 / 进程和线程 / 正则表达式

正则表达式 (Regular Expression)

正则表达式是一种强大的模式匹配工具,用于在文本中查找、替换、验证特定格式的数据。Python的 re 模块提供了完整的正则表达式支持。

基本概念

┌─────────────────────────────────────────────────────────────────┐
│                      正则表达式速查表                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  【字符类】                                                      │
│  .        任意字符(换行符除外)                                 │
│  \d       数字 [0-9]                                            │
│  \D       非数字 [^0-9]                                         │
│  \w       字母数字下划线 [a-zA-Z0-9_]                           │
│  \W       非字母数字下划线                                       │
│  \s       空白字符(空格、制表符、换行)                        │
│  \S       非空白字符                                             │
│  [abc]    字符集合:a、b 或 c                                   │
│  [^abc]   取反:除 a、b、c 外的字符                             │
│  [a-z]    范围:a 到 z                                          │
│                                                                 │
│  【数量词】                                                      │
│  *        0 次或多次                                             │
│  +        1 次或多次                                             │
│  ?        0 次或 1 次                                           │
│  {n}      恰好 n 次                                             │
│  {n,}     至少 n 次                                             │
│  {n,m}    n 到 m 次                                            │
│                                                                 │
│  【锚点】                                                        │
│  ^        字符串开头                                             │
│  $        字符串结尾                                             │
│  \b       单词边界                                               │
│  \B       非单词边界                                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         

re 模块基本函数

1. match - 从开头匹配

match() 从字符串的开头匹配,成功返回匹配对象,失败返回 None

Python
import re

# match 从字符串开头匹配
result = re.match(r"\d+", "123abc")
if result:
    print(result.group())   # "123"

# 匹配失败
result = re.match(r"\d+", "abc123")
print(result)   # None

# 常用场景:验证手机号格式
phone = "13812345678"
if re.match(r"1[3-9]\d{9}", phone):
    print("有效手机号") 

2. search - 搜索匹配

search() 在字符串中任意位置搜索第一个匹配。

Python
import re

# search 搜索第一个匹配
result = re.search(r"\d+", "abc123def456")
if result:
    print(result.group())   # "123"

# 查找邮箱
text = "我的邮箱是 test@example.com,请联系我"
email = re.search(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
if email:
    print(email.group())   # test@example.com 

3. findall - 查找所有匹配

findall() 返回字符串中所有匹配的列表。

Python
import re

# findall 返回所有匹配的列表
numbers = re.findall(r"\d+", "abc123def456ghi789")
print(numbers)   # ['123', '456', '789']

# 找出所有单词
words = re.findall(r"\w+", "Hello, World! Python 3.9")
print(words)   # ['Hello', 'World', 'Python', '3', '9']

# 找出所有邮箱
text = "联系人: a@test.com, b@test.com"
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(emails)   # ['a@test.com', 'b@test.com'] 

4. finditer - 迭代器版本

Python
import re

# finditer 返回迭代器,适合大数据量
text = "abc123def456ghi789"
for match in re.finditer(r"\d+", text):
    print(f"匹配: {match.group()}, 位置: {match.span()}") 

5. split - 分割

Python
import re

# 使用正则分割
parts = re.split(r"\s+", "hello   world  python")
print(parts)   # ['hello', 'world', 'python']

# 按数字分割
parts = re.split(r"\d+", "a1b2c3")
print(parts)   # ['a', 'b', 'c']

# 限制分割次数
parts = re.split(r",", "a,b,c,d", maxsplit=2)
print(parts)   # ['a', 'b', 'c,d'] 

6. sub - 替换

Python
import re

# 基本替换
text = "电话: 010-12345678"
result = re.sub(r"\d{3}-\d{8}", "XXX-XXXXXXXX", text)
print(result)   # 电话: XXX-XXXXXXXX

# 使用函数进行替换
def mask_phone(match):
    "将电话号码中间四位隐藏"
    phone = match.group()
    return phone[:3] + "****" + phone[-4:]

text = "张三:13812345678, 李四:13998765432"
result = re.sub(r"1[3-9]\d{9}", mask_phone, text)
print(result)   # 张三:138****5678, 李四:139****5432

# 替换次数限制
text = "a1b2c3"
result = re.sub(r"\d", "#", text, count=2)
print(result)   # a#b#c3 

compile 编译正则

对于重复使用的正则表达式,先编译可以提高效率。

Python
import re

# 编译正则表达式
phone_pattern = re.compile(r"1[3-9]\d{9}")
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")

# 使用编译后的模式
text = "电话: 13812345678, 邮箱: test@example.com"

phone = phone_pattern.search(text)
email = email_pattern.search(text)

print(phone.group())   # 13812345678
print(email.group())  # test@example.com

# 编译后的模式可以多次使用
phones = phone_pattern.findall("13812345678, 13912345678, 13712345678")
print(phones)   # ['13812345678', '13912345678', '13712345678'] 

分组 (Groups)

使用圆括号 () 创建分组,可以提取特定的子模式。

┌─────────────────────────────────────────────────────────────────┐
│                        分组示意图                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   正则: (\d{4})-(\d{2})-(\d{2})                                 │
│   文本: 2026-01-15                                              │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  完整匹配: 2026-01-15  → group(0) 或 group()            │   │
│   │  第1组: 2026            → group(1)                      │   │
│   │  第2组: 01              → group(2)                      │   │
│   │  第3组: 15              → group(3)                      │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   命名的分组: (?Ppattern)                                  │
│   (?P\d{4})-(?P\d{2})-(?P\d{2})               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         
Python
import re

# 基本分组
date = "2026-01-15"
match = re.match(r"(\d{4})-(\d{2})-(\d{2})", date)

print(match.group())      # 2026-01-15 (完整匹配)
print(match.group(1))   # 2026 (第一个分组)
print(match.group(2))   # 01 (第二个分组)
print(match.group(3))   # 15 (第三个分组)

# 具名分组
match = re.match(r"(?P\d{4})-(?P\d{2})-(?P\d{2})", date)
print(match.group("year"))   # 2026
print(match.group("month"))  # 01
print(match.group("day"))    # 15

# 提取所有分组
print(match.groups())     # ('2026', '01', '15')

# 常见应用:提取URL组成部分
url = "https://www.example.com:8080/path/to/page"
pattern = re.compile(r"(?Phttps?)://(?P[^:/]+):?(?P\d+)?(?P/.*)?")
m = pattern.match(url)
if m:
    print(f"协议: {m.group('scheme')}")
    print(f"域名: {m.group('domain')}")
    print(f"端口: {m.group('port')}")
    print(f"路径: {m.group('path')}") 

贪婪 vs 非贪婪

┌─────────────────────────────────────────────────────────────────┐
│                    贪婪 vs 非贪婪模式                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   贪婪模式: 尽可能多地匹配                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  文本: <div>hello</div>                                │   │
│   │  模式: <div>.*</div>                                   │   │
│   │  结果: hello (整个字符串!)                              │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   非贪婪模式: 尽可能少地匹配                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  文本: <div>hello</div>                                │   │
│   │  模式: <div>.*?</div>                                   │   │
│   │  结果: hello                                           │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   数量词后加 ? 变成非贪婪:                                       │
│   *?   +?   ??   {n,}?                                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         
Python
import re

# 贪婪匹配
text = "<div>hello</div><div>world</div>"
result = re.findall(r"<div>.*</div>", text)
print(f"贪婪: {result}")
# ['<div>hello</div><div>world</div>'] (匹配整个字符串)

# 非贪婪匹配
result = re.findall(r"<div>.*?</div>", text)
print(f"非贪婪: {result}")
# ['<div>hello</div>', '<div>world</div>']

# 其他非贪婪示例
text = "aaaa"
print(re.findall(r"a+", text))     # ['aaaa'] (贪婪)
print(re.findall(r"a+?", text))    # ['a', 'a', 'a', 'a'] (非贪婪) 

知识测验

问题1:re.match() 和 re.search() 的主要区别是?

问题2:如何匹配一个或多个数字但尽可能少(非贪婪)?

课后练习

✏️ 练习1:验证身份证号

编写正则表达式验证身份证号:

  • 15位数字(老版)或18位数字(新版)
  • 18位身份证最后一位可能是 X
  • 验证出生日期是否合理
✏️ 练习2:提取日志信息

从日志文本中提取信息:

  • 日志格式:[2026-01-15 10:30:45] ERROR: 连接失败 (192.168.1.100)
  • 提取日期、时间、级别、消息、IP地址
  • 使用具名分组
✏️ 练习3:文本清洗

实现文本清洗功能:

  • 去除多余空格
  • 将多个标点符号替换为单个
  • 统一日期格式(如 2026/01/15 → 2026-01-15)
  • 脱敏手机号(13812345678 → 138****5678)
🐍 Python 在线代码编辑器
📝 运行结果