正则表达式

Python正则表达式教程

正则表达式(Regular Expression)是一种强大的字符串匹配工具,它使用特定的语法规则来描述字符串模式。在Python中,我们可以通过re模块来使用正则表达式。

基础语法

基本匹配规则

  • 直接字符:精确匹配字符本身
  • \d:匹配一个数字(等价于[0-9]
  • \w:匹配一个字母、数字或下划线(等价于[a-zA-Z0-9_]
  • \s:匹配一个空白字符(包括空格、制表符等)
  • .:匹配任意一个字符(除了换行符)

数量限定符

  • *:匹配前面的字符0次或多次
  • +:匹配前面的字符1次或多次
  • ?:匹配前面的字符0次或1次
  • {n}:匹配前面的字符恰好n次
  • {n,}:匹配前面的字符至少n次
  • {n,m}:匹配前面的字符n到m次

示例:

'00\d'    # 匹配'007',但不匹配'00A'
'\d{3}'   # 匹配'010'
'\w\w\d'  # 匹配'py3'
'py.'     # 匹配'pyc'、'pyo'、'py!'等

字符集和范围

  • [abc]:匹配a、b或c中的任意一个字符
  • [a-z]:匹配任意小写字母
  • [0-9a-zA-Z_]:匹配数字、字母或下划线
  • [^abc]:匹配除了a、b、c之外的任意字符

示例:

'[0-9a-zA-Z_]+'    # 匹配由数字、字母或下划线组成的字符串
'[a-zA-Z_]\w*'     # 匹配Python合法变量名

边界匹配

  • ^:匹配字符串的开头
  • $:匹配字符串的结尾
  • \b:匹配单词边界

示例:

'^\d+'    # 匹配以数字开头的字符串
'\d+$'    # 匹配以数字结尾的字符串
'^py$'    # 精确匹配'py'

分组和或操作

  • (pattern):捕获分组
  • (?:pattern):非捕获分组
  • A|B:匹配A或B

示例:

'(P|p)ython'    # 匹配'Python'或'python'
'(\d{3})-(\d{3,8})'  # 分组匹配电话号码

Python中的re模块

基本使用

import re

# 使用r前缀避免转义问题
pattern = r'\d{3}-\d{3,8}'

# 匹配检查
if re.match(pattern, '010-12345'):
    print('匹配成功')
else:
    print('匹配失败')

常用方法

  1. re.match(pattern, string):从字符串开头匹配
  2. re.search(pattern, string):搜索整个字符串
  3. re.findall(pattern, string):返回所有匹配项
  4. re.finditer(pattern, string):返回匹配迭代器
  5. re.split(pattern, string):按模式分割字符串
  6. re.sub(pattern, repl, string):替换匹配项

分组提取

m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
print(m.group(0))  # '010-12345' (整个匹配)
print(m.group(1))  # '010' (第一个分组)
print(m.group(2))  # '12345' (第二个分组)
print(m.groups())  # ('010', '12345') (所有分组)

编译正则表达式

对于需要重复使用的正则表达式,可以先编译:

re_phone = re.compile(r'^(\d{3})-(\d{3,8})$')
result = re_phone.match('010-12345').groups()  # ('010', '12345')

高级技巧

非贪婪匹配

默认情况下,正则表达式会尽可能多地匹配(贪婪模式),添加?可以改为非贪婪模式:

# 贪婪匹配
re.match(r'^(\d+)(0*)$', '102300').groups()  # ('102300', '')

# 非贪婪匹配
re.match(r'^(\d+?)(0*)$', '102300').groups()  # ('1023', '00')

验证时间格式

pattern = r'^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$'
m = re.match(pattern, '19:05:30')
print(m.groups())  # ('19', '05', '30')

验证Email地址

def is_valid_email(addr):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, addr))

# 测试
assert is_valid_email('someone@gmail.com')
assert is_valid_email('bill.gates@microsoft.com')
assert not is_valid_email('bob#example.com')
assert not is_valid_email('mr-bob@example.com')

提取带名字的Email

def name_of_email(addr):
    m = re.match(r'^(?:<([^>]+)>)?\s*([a-zA-Z0-9._%+-]+)@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', addr)
    return m.group(1) if m.group(1) else m.group(2)

# 测试
assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris'
assert name_of_email('tom@voyager.org') == 'tom'

实际应用示例

分割字符串

# 普通分割(无法处理连续分隔符)
'a b  c'.split(' ')  # ['a', 'b', '', 'c']

# 正则分割
re.split(r'\s+', 'a b  c')  # ['a', 'b', 'c']
re.split(r'[\s,;]+', 'a,b;; c d')  # ['a', 'b', 'c', 'd']

查找和替换

# 查找所有数字
re.findall(r'\d+', '电话1234,手机5678')  # ['1234', '5678']

# 替换
re.sub(r'\d+', 'NUM', '电话1234,手机5678')  # '电话NUM,手机NUM'

最佳实践

  1. 使用r前缀避免转义问题
  2. 复杂的正则表达式添加注释(Python 3.x支持re.VERBOSE
  3. 对于频繁使用的正则表达式,先编译再使用
  4. 考虑使用第三方库如regex(比标准库re功能更强大)
# 带注释的正则表达式
pattern = re.compile(r"""
    ^                   # 字符串开始
    [a-zA-Z0-9._%+-]+   # 用户名
    @                   # @符号
    [a-zA-Z0-9.-]+      # 域名
    \.                  # 点
    [a-zA-Z]{2,}        # 顶级域名
    $                   # 字符串结束
""", re.VERBOSE)

正则表达式是一个强大的工具,但也容易变得复杂难懂。建议从简单模式开始,逐步构建复杂的表达式,并始终进行充分的测试。