—— 复杂匹配/替换/性能优化
import re
# 基础分组:用编号访问
m = re.match(r"(\d{4})-(\d{2})-(\d{2})", "2024-03-15")
print(m.group(1)) # 2024
print(m.group(2)) # 03
# 命名分组:用名字访问,更清晰
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "2024-03-15")
print(m.group("year")) # 2024
print(m.group("month")) # 03
print(m.group("day")) # 15
# groupdict() 一次获取所有命名分组
print(m.groupdict())
# {'year': '2024', 'month': '03', 'day': '15'}
# 反向引用:匹配重复单词
text = "this is is a test test string"
duplicates = re.findall(r"\b(\w+)\s+\1\b", text)
print(duplicates) # ['is', 'test']
# 命名反向引用
pattern = r"(?P<word>\w+)\s+(?P=word)"
m = re.search(pattern, "hello hello")
print(m.group("word")) # hello
import re
# 正向前瞻:(?=pattern)
text = "价格100元,折扣20元"
prices = re.findall(r"\d+(?=元)", text)
print(prices) # ['100', '20']
# 负向前瞻:(?!pattern)
files = "photo.jpg logo.png icon.svg banner.png"
non_png = re.findall(r"\b\w+\.(?!png)\w+", files)
print(non_png) # ['photo.jpg', 'icon.svg']
# 正向后顾:(?<=pattern)
html = "<h1>标题</h1> <p>段落</p>"
contents = re.findall(r"(?<=<\w+>).+?(?=</\w+>)", html)
print(contents) # ['标题', '段落']
# 负向后顾:(?<!pattern)
text = "price: $100, count: 50, $30, qty: 20"
nums = re.findall(r"(?<!\$)\b\d+", text)
print(nums) # ['50', '20']
# 实战:密码强度检查
def check_password_strength(password):
"""使用环视断言检查密码强度"""
checks = {
"长度≥8": bool(re.search(r".{8,}", password)),
"含大写": bool(re.search(r"(?=.*[A-Z])", password)),
"含小写": bool(re.search(r"(?=.*[a-z])", password)),
"含数字": bool(re.search(r"(?=.*\d)", password)),
"含特殊字符": bool(re.search(r"(?=.*[!@#$%^&*])", password)),
}
score = sum(checks.values())
for k, v in checks.items():
print(f" {'✅' if v else '❌'} {k}")
return score
import re
# 回调函数替换:银行卡号脱敏
def mask_card(match):
"""保留前4后4"""
card = match.group()
return card[:4] + "*" * (len(card) - 8) + card[-4:]
result = re.sub(r"\d{16,19}", mask_card, "卡号6225881234567890")
print(result) # 卡号6225********7890
# 手机号脱敏
def mask_phone(match):
phone = match.group()
return phone[:3] + "****" + phone[-4:]
text = "联系方式:13812345678,备用:15987654321"
result = re.sub(r"1[3-9]\d{9}", mask_phone, text)
print(result) # 联系方式:138****5678,备用:159****4321
# 模板变量替换
template = "Hello {{name}}, welcome to {{city}}!"
data = {"name": "张三", "city": "北京"}
result = re.sub(r"\{\{(\w+)\}\}", lambda m: data.get(m.group(1), m.group(0)), template)
print(result) # Hello 张三, welcome to 北京!
# 驼峰转下划线
camel = "myVariableName"
snake = re.sub(r"(?<!^)(?=[A-Z])", "_", camel).lower()
print(snake) # my_variable_name
import re
# URL 匹配
url_pattern = re.compile(
r"https?://"
r"(?:[\w-]+\.)+[\w]{2,}" # 域名
r"(?::\d+)?" # 端口
r"(?:/[^\s]*)?" # 路径
)
# 邮箱匹配(实用版)
email_pattern = re.compile(
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
)
# IPv4 匹配
ipv4_pattern = re.compile(
r"(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}"
r"(?:25[0-5]|2[0-4]\d|[01]?\d\d?)"
)
# 中国手机号
phone_pattern = re.compile(r"1[3-9]\d{9}")
# 中国身份证号(18位)
id_pattern = re.compile(
r"[1-9]\d{5}" # 地区码
r"(?:19|20)\d{2}" # 出生年
r"(?:0[1-9]|1[0-2])" # 月
r"(?:0[1-9]|[12]\d|3[01])" # 日
r"\d{3}" # 顺序码
r"[\dXx]" # 校验码
)
# VERBOSE 模式:可读正则
verbose_url = re.compile(r"""
https?:// # 协议
(?:[\w-]+\.)+[\w]{2,} # 域名部分
(?::\d+)? # 可选端口
(?:/[^\s]*)? # 可选路径
""", re.VERBOSE)
# 测试
text = "访问 https://example.com:8080/api 或 http://test.cn 联系 admin@mail.com"
print("URLs:", url_pattern.findall(text))
print("Emails:", email_pattern.findall(text))
import re
# 1. 预编译正则——循环中极重要
pattern = re.compile(r"\b\w+@\w+\.\w+\b")
# 2. 避免灾难性回溯——嵌套量词 (a+)+ 极易指数级回溯
# Python 3.11+ 支持 atomic 分组 (?>...)
# 3. 否定字符类 > 非贪婪匹配
# 慢:'".*?"' 快:'"[^"]*"'
slow = re.compile(r'".*?"')
fast = re.compile(r'"[^"]*"')
# 4. finditer 惰性生成,大量匹配时省内存
matches = list(pattern.finditer("contact: a@b.com and c@d.com"))
for m in matches:
print(f" 找到: {m.group()} 位置: {m.start()}-{m.end()}")
#!/usr/bin/env python3
"""第23课 正则进阶验证"""
import re
def test_named_groups():
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2024-03")
assert m.group("year") == "2024"
assert m.group("month") == "03"
assert m.groupdict() == {"year": "2024", "month": "03"}
print("✅ 命名分组测试通过")
def test_lookaround():
result = re.findall(r"\d+(?=元)", "100元 200刀 300元")
assert result == ["100", "300"]
result = re.findall(r"(?<=: )\d+", "port: 8080, pid: 1234")
assert "8080" in result and "1234" in result
print("✅ 环视断言测试通过")
def test_sub_callback():
def double(match):
return str(int(match.group()) * 2)
result = re.sub(r"\d+", double, "3 apples and 5 oranges")
assert result == "6 apples and 10 oranges"
print("✅ 回调替换测试通过")
def test_precompiled():
pattern = re.compile(r"\b\w{5}\b")
result = pattern.findall("hello world python regex advanced")
assert "hello" in result and "world" in result
print("✅ 预编译性能测试通过")
def test_real_world_patterns():
email_re = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
assert email_re.search("test@example.com")
assert not email_re.search("not-an-email")
phone_re = re.compile(r"1[3-9]\d{9}")
assert phone_re.search("13812345678")
assert not phone_re.search("12345678901")
print("✅ 实战模式测试通过")
if __name__ == "__main__":
test_named_groups()
test_lookaround()
test_sub_callback()
test_precompiled()
test_real_world_patterns()
print("\n🎉 第23课全部验证通过!")
import re
# 1. 逐步调试匹配
text = "订单号:ORD-2024-0315-8899"
pattern = r"ORD-(\d{4})-(\d{4})-(\d{4})"
m = re.search(pattern, text)
if m:
print(f"完整匹配: {m.group(0)}")
print(f"年份: {m.group(1)}")
print(f"月日: {m.group(2)}")
print(f"序号: {m.group(3)}")
print(f"位置: {m.start()}-{m.end()}")
# 2. 常见正则陷阱
# 陷阱1:贪婪匹配吃太多
text = "<div>A</div><div>B</div>"
wrong = re.findall(r"<div>.*</div>", text)
right = re.findall(r"<div>.*?</div>", text)
# 陷阱2:点号不匹配换行
text = "line1
line2"
match = re.findall(r"line1.line2", text, re.DOTALL)
# 陷阱3:^$ 与多行模式
text = "first
second
third"
single = re.findall(r"^\w+", text) # ['first']
multi = re.findall(r"^\w+", text, re.MULTILINE) # ['first', 'second', 'third']
import re
from collections import Counter
from dataclasses import dataclass
from typing import List
@dataclass
class LogEntry:
ip: str
timestamp: str
method: str
path: str
status: int
size: int
LOG_PATTERN = re.compile(
r'(\S+) - - '
r'\[([^\]]+)\] '
r'"(\S+) (\S+) \S+" '
r'(\d+) (\d+)'
)
def parse_log(content: str) -> List[LogEntry]:
entries = []
for line in content.strip().split("
"):
m = LOG_PATTERN.match(line)
if m:
entries.append(LogEntry(
ip=m.group(1), timestamp=m.group(2),
method=m.group(3), path=m.group(4),
status=int(m.group(5)), size=int(m.group(6)),
))
return entries
def analyze_logs(entries: List[LogEntry]):
print(f"总请求数: {len(entries)}")
print(f"唯一IP: {len(set(e.ip for e in entries))}")
print(f"状态码分布: {dict(Counter(e.status for e in entries))}")
print(f"Top5路径: {Counter(e.path for e in entries).most_common(5)}")
errors = [e for e in entries if e.status >= 400]
print(f"错误率: {len(errors)/len(entries)*100:.1f}%")
import re
# 匹配中文字符
text = "Hello世界Python编程"
chinese = re.findall(r"[一-鿿]+", text)
print(chinese) # ['世界', '编程']
# 匹配中文标点
punct = re.findall(r"[,。!?、;:""''()【】]", "你好,世界!测试。")
print(punct) # [',', '!', '。']
# 匹配 emoji
emoji_pattern = re.compile(
"[😀-🙏" # emoticons
"🌀-🗿" # symbols & pictographs
"🚀-" # transport & map
"-🇿" # flags
"]+", flags=re.UNICODE
)
text = "你好👋世界🌍Python🐍"
emojis = emoji_pattern.findall(text)
print(emojis) # ['👋', '🌍', '🐍']
# 去除 emoji
clean = emoji_pattern.sub("", text)
print(clean) # 你好世界Python
# \w 在 re.UNICODE 下匹配中文
words = re.findall(r"\w+", text, re.UNICODE)
print(words) # ['你好', '世界', 'Python']
# 按字符类型分割
text = "Python3.9版本2024年3月"
parts = re.findall(r"[a-zA-Z]+|\d+|[^\w]+", text)
print(parts) # ['Python', '3', '.', '9', '版本', '2024', '年', '3', '月']
| 方法 | 用途 | 返回 |
|---|---|---|
| re.match() | 从开头匹配 | Match 或 None |
| re.search() | 搜索第一个匹配 | Match 或 None |
| re.findall() | 所有匹配 | 列表 |
| re.finditer() | 所有匹配(惰性) | 迭代器 |
| re.sub() | 替换 | 新字符串 |
| re.split() | 分割 | 列表 |
| re.compile() | 预编译 | Pattern 对象 |
"""推荐正则可视化工具:
1. regex101.com —— 在线测试 + 解释
2. debuggex.com —— 可视化正则流程图
3. regexr.com —— 实时匹配 + 语法参考
4. Python re.DEBUG —— 内置编译调试
使用 regex101.com 的技巧:
- 选择 Python flavor
- 开启 Explanation 面板
- 保存分享链接给同事
- 用单元测试验证边界情况
"""