Python: 正则表达式
正则表达式是处理文本数据的强大工具,Python通过re
模块提供了完整的正则表达式功能。本文将详细介绍Python正则表达式的使用方法,包括基础语法、高级技巧和re
模块API的详细解析。
一、正则表达式基础
1.1 什么是正则表达式
正则表达式(Regular Expression)是一种用于匹配字符串中字符组合的模式,可以用于搜索、替换和验证文本数据。
1.2 Python中的re模块
Python通过内置的re
模块提供正则表达式支持:
import re
二、正则表达式基本语法
2.1 普通字符
大多数字母和字符只会匹配它们自身:
pattern = r"hello"
text = "hello world"
match = re.search(pattern, text)
if match:print("找到匹配:", match.group()) # 输出: 找到匹配: hello
2.2 元字符
正则表达式中具有特殊含义的字符:
-
.
匹配任意单个字符(除了换行符) -
^
匹配字符串的开头 -
$
匹配字符串的结尾 -
*
匹配前面的子表达式零次或多次 -
+
匹配前面的子表达式一次或多次 -
?
匹配前面的子表达式零次或一次 -
{m,n}
匹配前面的子表达式m到n次 -
[]
字符集,匹配其中任意一个字符 -
|
或操作,匹配左边或右边的表达式 -
()
分组,标记一个子表达式的开始和结束位置
2.3 字符类
-
\d
匹配任意数字,等价于[0-9] -
\D
匹配任意非数字字符 -
\s
匹配任意空白字符(空格、制表符、换行符等) -
\S
匹配任意非空白字符 -
\w
匹配任意字母数字字符,等价于[a-zA-Z0-9_] -
\W
匹配任意非字母数字字符
三、re模块API详解
3.1 re.compile(pattern, flags=0)
编译正则表达式模式,返回一个正则表达式对象。
参数说明:
-
pattern
: 要编译的正则表达式字符串 -
flags
: 可选标志,用于修改正则表达式的匹配方式
常用flags:
-
re.IGNORECASE
或re.I
: 忽略大小写 -
re.MULTILINE
或re.M
: 多行模式,影响^和$ -
re.DOTALL
或re.S
: 使.匹配包括换行符在内的所有字符
示例:
# 编译一个正则表达式对象
pattern = re.compile(r'\d{3}-\d{3}-\d{4}', re.IGNORECASE)# 使用编译后的对象进行匹配
text = "我的电话号码是123-456-7890"
match = pattern.search(text)
if match:print("找到电话号码:", match.group()) # 输出: 找到电话号码: 123-456-7890
3.2 re.search(pattern, string, flags=0)
扫描整个字符串并返回第一个成功的匹配。
参数说明:
-
pattern
: 要匹配的正则表达式 -
string
: 要搜索的字符串 -
flags
: 可选标志
示例:
text = "Python是一种流行的编程语言,Python简单易学"
match = re.search(r'Python', text)
if match:print("找到匹配:", match.group()) # 输出: 找到匹配: Pythonprint("匹配位置:", match.span()) # 输出: 匹配位置: (0, 6)
3.3 re.match(pattern, string, flags=0)
尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,就返回None。
与search的区别:
-
match
只在字符串开头匹配 -
search
在整个字符串中搜索第一个匹配
示例:
text1 = "Python很棒"
text2 = "学习Python很棒"print(re.match(r'Python', text1)) # 返回匹配对象
print(re.match(r'Python', text2)) # 返回None
3.4 re.findall(pattern, string, flags=0)
返回字符串中所有与模式匹配的非重叠匹配项,作为字符串列表。
示例:
text = "苹果10元,香蕉5元,橙子8元"
prices = re.findall(r'\d+元', text)
print(prices) # 输出: ['10元', '5元', '8元']
3.5 re.finditer(pattern, string, flags=0)
返回一个迭代器,产生所有非重叠匹配的匹配对象。
与findall的区别:
-
findall
返回字符串列表 -
finditer
返回匹配对象迭代器
示例:
text = "Python 3.8, Python 3.9, Python 3.10"
matches = re.finditer(r'Python \d+\.\d+', text)
for match in matches:print(f"找到: {match.group()} 在位置 {match.span()}")
# 输出:
# 找到: Python 3.8 在位置 (0, 9)
# 找到: Python 3.9 在位置 (11, 20)
# 找到: Python 3.10 在位置 (22, 32)
3.6 re.sub(pattern, repl, string, count=0, flags=0)
替换字符串中的匹配项。
参数说明:
-
pattern
: 正则表达式模式 -
repl
: 替换的字符串或函数 -
string
: 原始字符串 -
count
: 最大替换次数,0表示替换所有 -
flags
: 可选标志
示例:
text = "今天是2023-05-15,明天是2023-05-16"
# 替换日期格式
new_text = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\1年\2月\3日', text)
print(new_text) # 输出: 今天是2023年05月15日,明天是2023年05月16日# 使用函数作为替换
def to_upper(match):return match.group().upper()text = "hello world"
new_text = re.sub(r'\w+', to_upper, text)
print(new_text) # 输出: HELLO WORLD
3.7 re.split(pattern, string, maxsplit=0, flags=0)
按照能够匹配的子串将字符串分割后返回列表。
参数说明:
-
pattern
: 分隔符正则表达式 -
string
: 要分割的字符串 -
maxsplit
: 最大分割次数,0表示不限制 -
flags
: 可选标志
示例:
text = "苹果,香蕉,,橙子, 西瓜"
# 按逗号分割,忽略空格和空字符串
items = re.split(r'\s*,\s*', text.strip())
print(items) # 输出: ['苹果', '香蕉', '', '橙子', '西瓜']# 使用多个分隔符
text = "苹果 香蕉,橙子;西瓜"
items = re.split(r'[ ,;]', text)
print(items) # 输出: ['苹果', '香蕉', '橙子', '西瓜']
四、匹配对象的方法
当使用search()
或match()
成功匹配后,会返回一个匹配对象,该对象有以下方法:
4.1 group([group1, ...])
返回匹配的一个或多个子组。
示例:
text = "John Doe, 30岁"
match = re.search(r'(\w+) (\w+), (\d+)岁', text)
if match:print("完整匹配:", match.group(0)) # 输出: 完整匹配: John Doe, 30岁print("名字:", match.group(1)) # 输出: 名字: Johnprint("姓氏:", match.group(2)) # 输出: 姓氏: Doeprint("年龄:", match.group(3)) # 输出: 年龄: 30print("所有组:", match.groups()) # 输出: 所有组: ('John', 'Doe', '30')
4.2 groups(default=None)
返回一个包含所有子组的元组。
4.3 groupdict(default=None)
返回一个包含所有命名子组的字典,键为子组名。
4.4 start([group]) 和 end([group])
返回匹配的子组的开始和结束位置。
4.5 span([group])
返回一个元组包含匹配的子组的 (开始, 结束) 位置。
五、高级正则表达式技巧
5.1 非贪婪匹配
默认情况下,*
和+
是贪婪的,会匹配尽可能多的字符。添加?
使其变为非贪婪:
text = "<h1>标题</h1><p>段落</p>"
# 贪婪匹配
greedy = re.search(r'<.*>', text)
print(greedy.group()) # 输出: <h1>标题</h1><p>段落</p># 非贪婪匹配
non_greedy = re.search(r'<.*?>', text)
print(non_greedy.group()) # 输出: <h1>
5.2 前向断言和后向断言
-
(?=...)
正向前视断言 -
(?!...)
负向前视断言 -
(?<=...)
正向后视断言 -
(?<!...)
负向后视断言
示例:
# 匹配后面跟着"元"的数字
text = "苹果10元,香蕉5元,橙子8个"
prices = re.findall(r'\d+(?=元)', text)
print(prices) # 输出: ['10', '5']# 匹配前面是"价格:"的数字
text = "价格:100,数量:5"
numbers = re.findall(r'(?<=价格:)\d+', text)
print(numbers) # 输出: ['100']
5.3 命名组
使用(?P<name>...)
语法为组命名:
text = "2023-05-15"
match = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
if match:print(match.groupdict()) # 输出: {'year': '2023', 'month': '05', 'day': '15'}
5.4 条件匹配
使用(?(id/name)yes-pattern|no-pattern)
:
# 如果第一个组匹配"Mr",则匹配"Smith",否则匹配"Smithson"
text1 = "Mr Smith"
text2 = "Mrs Smithson"
pattern = r'(Mr)? (?(1)Smith|Smithson)'print(re.match(pattern, text1)) # 匹配
print(re.match(pattern, text2)) # 匹配
六、实际应用示例
6.1 验证电子邮件地址
def validate_email(email):pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'return re.match(pattern, email) is not Noneprint(validate_email("test@example.com")) # True
print(validate_email("invalid.email@")) # False
6.2 提取URL信息
def extract_url_info(url):pattern = r'(https?)://([^/]+)(/.*)?'match = re.match(pattern, url)if match:return {'protocol': match.group(1),'domain': match.group(2),'path': match.group(3) or '/'}return Noneurl_info = extract_url_info("https://www.example.com/path/to/page")
print(url_info)
# 输出: {'protocol': 'https', 'domain': 'www.example.com', 'path': '/path/to/page'}
6.3 日志分析
log_line = '127.0.0.1 - - [10/May/2023:15:32:45 +0800] "GET /index.html HTTP/1.1" 200 1234'pattern = r'^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+) (\d+)'
match = re.match(pattern, log_line)if match:log_data = {'ip': match.group(1),'time': match.group(2),'method': match.group(3),'path': match.group(4),'protocol': match.group(5),'status': int(match.group(6)),'size': int(match.group(7))}print(log_data)# 输出: {'ip': '127.0.0.1', 'time': '10/May/2023:15:32:45 +0800', # 'method': 'GET', 'path': '/index.html', 'protocol': 'HTTP/1.1',# 'status': 200, 'size': 1234}
七、性能优化建议
-
预编译正则表达式:对于重复使用的正则表达式,使用
re.compile()
预先编译。 -
使用非贪婪匹配:当可能时,使用非贪婪限定符
*?
、+?
等。 -
避免回溯灾难:复杂的正则表达式可能导致性能问题,尽量简化。
-
使用原子组:
(?>...)
可以防止回溯。 -
合理使用字符类:
[abc]
比(a|b|c)
更高效。
八、常见问题与解决方案
8.1 匹配多行文本
使用re.MULTILINE
标志:
text = """第一行
第二行
第三行"""
matches = re.findall(r'^第\w+', text, re.MULTILINE)
print(matches) # 输出: ['第一行', '第二行', '第三行']
8.2 忽略大小写匹配
使用re.IGNORECASE
标志:
text = "Python python PYTHON"
matches = re.findall(r'python', text, re.IGNORECASE)
print(matches) # 输出: ['Python', 'python', 'PYTHON']
8.3 匹配Unicode字符
使用\u
或\x
转义,或直接包含Unicode字符:
text = "中文Chinese にほんご"
matches = re.findall(r'[\u4e00-\u9fa5]+', text) # 匹配中文字符
print(matches) # 输出: ['中文']
九、总结
Python的正则表达式功能强大而灵活,re
模块提供了丰富的API来处理各种文本匹配需求。掌握正则表达式可以大大提高文本处理的效率和能力。记住:
-
复杂的正则表达式可以先分解为多个简单的部分
-
使用
re.VERBOSE
标志可以使复杂的正则表达式更易读 -
测试正则表达式时可以使用在线工具如regex101.com
-
对于非常复杂的文本处理,可能需要结合其他方法(如解析器)
希望本博客能帮助你掌握Python正则表达式的使用!