正则表达式在文本处理领域占据着举足轻重的地位,它能够帮助开发者高效地进行复杂字符串的匹配、提取和替换操作。本教程聚焦于 Python 中的正则表达式,以re
模块为核心,从基础概念到高级应用,全方位深入剖析其使用技巧。无论是编程新手还是经验丰富的开发者,都能通过丰富的示例代码、清晰的图文及表格对比,获取实用知识,大幅提升文本处理能力。
正则表达式基础概念
-
定义与用途:正则表达式是嵌入 Python 并通过
re
模块提供的一种小型、高度专业化的编程语言,用于定义字符串匹配规则。它可用于匹配英文句子、邮箱地址等各类字符串,还能进行字符串修改和拆分。 -
匹配字符
-
普通字符匹配:大多数字母和符号会直接匹配自身,如
test
就匹配test
。 -
元字符匹配:像
[
、]
、\
等元字符有特殊含义。[
和]
用于指定字符类,如[abc]
匹配a
、b
、c
中任意一个字符,等同于[a - c]
;^
在字符类开头表示取反,如[^5]
匹配除5
以外的字符 。反斜杠\
用于表示特殊序列或转义元字符,如\w
匹配字母数字字符,在bytes
类型中相当于[a-zA-Z0-9_]
,在str
类型中根据unicodedata
模块匹配 Unicode 数据库中的字母字符。常见特殊序列包括\d
(匹配数字,等价于[0-9]
)、\D
(匹配非数字字符,等价于[^0-9]
)、\s
(匹配空白字符,等价于[\t\n\r\f\v]
)、\S
(匹配非空白字符,等价于[^\t\n\r\f\v]
)、\w
(匹配字母数字字符,等价于[a-zA-Z0-9_]
)、\W
(匹配非字母数字字符,等价于[a-zA-Z0-9_]
)。[.
匹配除换行符外的任何字符,在re.DOTALL
模式下可匹配换行符。
-
元字符详解
-
字符类相关元字符:
[
和]
用于创建字符类,定义匹配的字符集合。例如在一个处理文本数据的项目中,需要提取所有的元音字母,就可以使用[aeiou]
这个正则表达式。假设文本内容为"hello world"
,使用re.findall('[aeiou]', "hello world")
,就能得到['e', 'o', 'o']
。在字符类中,元字符(除\
外)失去特殊含义,如[akm$]
匹配a
、k
、m
或$
。^
在字符类开头表示取反,在验证用户输入的密码强度时,要求密码不能包含特殊字符(这里假设特殊字符为除字母、数字和下划线之外的字符),可以用re.search('[^a-zA-Z0-9_]', password)
来检查,如果匹配到内容,说明密码包含特殊字符,不符合要求。 -
重复相关元字符:
*
表示前一个字符可匹配零次或更多次,在解析数学表达式时,比如解析形如x^n
(n
为整数)的幂运算表达式,x\^[0-9]*
就可以匹配x^
后面跟着任意个数字的情况,像x^2
、x^100
等都能匹配。+
表示匹配一次或更多次,在验证手机号码格式时,国内手机号码第一位是1
,第二位可以是3
、4
、5
、6
、7
、8
、9
,后面跟着 9 位数字,1[3-9]\d{9}
就能准确匹配手机号码,这里\d{9}
表示匹配 9 个数字,+
类似,但+
要求至少出现一次,比如匹配连续的数字,\d+
可以匹配像123
、45678
等连续数字的字符串。?
表示匹配一次或零次,在处理一些可能有后缀的文件名时,比如readme.txt
和readme
,readme\.txt?
就可以同时匹配这两种情况,txt
后的?
表示txt
这个后缀可以出现一次或不出现。{m,n}
表示至少重复m
次,至多重复n
次,a{1,3}
匹配a
、aa
、aaa
;在解析网页标签属性时,假设某个属性的值允许 1 到 3 个字母,[a-zA-Z]{1,3}
就可以用来匹配该属性值。{m}
表示与前一项完全匹配m
次,a{2}
只匹配aa
,在处理固定格式的验证码时,如果验证码是两位相同的数字,\d{2}
(这里假设验证码是数字)就可以用来验证格式是否正确。 -
位置相关元字符:
^
在默认情况下匹配字符串开头,在检查用户输入的命令是否以特定单词开头时,比如判断用户输入是否是"search "
开头,^search\s
就能实现这个功能,其中\s
表示匹配空白字符。在re.MULTILINE
模式下还可匹配每行开头,在处理日志文件时,每行日志都有一个时间戳,格式为[年-月-日 时:分:秒]
,要提取每行日志的时间戳,re.findall(r'^\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\]', log_text, re.MULTILINE)
就可以做到。$
匹配行末尾,在re.MULTILINE
模式下还可匹配每行末尾,World$
匹配以World
结尾的字符串。在处理配置文件时,假设配置项以=value
结尾,=value$
就可以用来查找这类配置项。\A
仅匹配字符串开头,\Z
只匹配字符串尾。在验证用户输入的完整字符串是否是特定内容时,\Ahello\Z
可以精确匹配字符串hello
,多一个或少一个字符都无法匹配。\b
是字边界,匹配单词开头或结尾,\bword\b
精确匹配单词word
,在文本编辑软件中,实现查找某个单词功能时,\b
可以确保只匹配完整的单词,而不是单词的一部分,比如查找"the"
,不会匹配到"thesis"
中的"the"
。\B
与\b
相反,匹配不在字边界的位置,在处理一些缩写词时,假设要匹配缩写词内部的字符,而不是单词边界上的字符,就可以使用\B
,比如\BMr\B
可以匹配"Mr."
中的"Mr"
,但不会匹配单独的"Mr"
单词。 -
逻辑相关元字符:
|
是 “或” 运算符,在一个处理多种文件类型的程序中,需要匹配图片文件的扩展名,jpg|png|gif
就可以匹配jpg
、png
或gif
这三种扩展名,用来判断文件是否为图片文件。 -
其他元字符:
\
用于转义元字符,使元字符匹配自身,如\[
匹配[
;还用于表示特殊序列。在解析 LaTeX 公式时,经常会遇到需要匹配特殊符号的情况,比如匹配\sum
这个符号,就需要使用r'\\sum'
,这里第一个\
是 Python 字符串转义,第二个\
是正则表达式转义,确保匹配到\sum
。.
匹配除换行符外的任何字符,在re.DOTALL
模式下可匹配换行符,a.b
可匹配a
和b
之间有任意一个字符(除换行符)的情况,在re.DOTALL
模式下也可匹配包含换行符的情况。在处理一些文本片段时,如果要匹配a
和b
之间有任意字符(包括换行符)的情况,在设置re.DOTALL
标志后,re.search('a.*b', text_with_newline, re.DOTALL)
就可以实现。
重复匹配
-
重复元字符:
*
表示前一个字符可匹配零次或更多次,如ca*t
可匹配ct
、cat
、caaat
等;+
表示匹配一次或更多次,如ca+t
可匹配cat
、caaat
,但不能匹配ct
;?
表示匹配一次或零次,如home-?brew
可匹配homebrew
或home-brew
;{m,n}
表示至少重复m
次,至多重复n
次,如a/{1,3}b
匹配a/b
、a//b
、a///b
,{m}
表示与前一项完全匹配m
次,如a/{2}b
只匹配a//b
。 -
贪婪与非贪婪匹配:重复匹配默认是贪婪的,匹配引擎会尽可能多地匹配,若后续部分不匹配则回退重试。如
a[bcd]*b
匹配abcbd
时,先尽可能多地匹配[bcd]*
,失败后逐步回退。非贪婪匹配使用*?
、+?
、??
、{m,n}?
,会尽可能少地匹配文本。如匹配 HTML 标签<.*>
会匹配整个字符串,而<.*?>
则只匹配第一个标签 。
在 Python 中使用正则表达式
-
编译正则表达式:使用
re.compile()
将正则表达式编译为模式对象,模式对象有多种操作方法。如p = re.compile('ab*')
,还可传入flags
参数启用特殊功能,如p = re.compile('ab*', re.IGNORECASE)
实现不区分大小写匹配。由于正则表达式中反斜杠与 Python 字符串转义冲突,建议使用原始字符串表示法,如r"\section"
。 -
应用匹配
-
匹配方法:模式对象的
match()
用于确定正则是否从字符串开头匹配,search()
用于扫描字符串查找匹配位置,findall()
返回所有匹配子字符串的列表,finditer()
返回匹配对象的迭代器。若未找到匹配,match()
和search()
返回None
,成功则返回匹配对象,包含匹配相关信息 。 -
匹配对象属性和方法:匹配对象的
group()
返回匹配的字符串,start()
返回匹配开始位置,end()
返回匹配结束位置,span()
返回包含开始和结束位置的元组 。
-
-
模块级函数:
re
模块提供的match()
、search()
、findall()
、sub()
等顶级函数,功能与模式对象方法类似,只是将正则字符串作为第一个参数,并会缓存编译对象。循环中使用正则表达式时,预编译可节省函数调用开销;循环外使用,两者差异不大。 -
编译标志:编译标志可修改正则表达式的工作方式,如
re.IGNORECASE
实现不区分大小写匹配,re.DOTALL
使.
匹配包括换行符在内的任何字符,re.VERBOSE
允许编写更易读的正则表达式,忽略正则字符串中的空格(字符类中或反斜杠前的空格除外),并支持添加注释。
编译标志 | 含义 | 示例 |
---|---|---|
re.ASCII /re.A | 使\w 、\b 、\s 、\d 等只匹配 ASCII 字符 | re.compile(r'\w+', re.ASCII) ,只匹配 ASCII 字母数字字符 |
re.DOTALL /re.S | 使. 匹配任何字符,包括换行符 | re.compile(r'.*', re.DOTALL) ,可匹配包含换行符的字符串 |
re.IGNORECASE /re.I | 进行大小写不敏感匹配 | re.compile(r'[A-Z]', re.IGNORECASE) ,可匹配大小写字母 |
re.LOCALE /re.L | 进行区域设置感知匹配(Python 3 中不鼓励使用) | 在特定区域设置下,\w 匹配特定区域的字母字符 |
re.MULTILINE /re.M | 多行匹配,影响^ 和$ | re.compile(r'^From', re.MULTILINE) ,可匹配每行开头的From |
re.VERBOSE /re.X | 启用详细的正则表达式,忽略空格和注释 | re.compile(r'\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$', re.VERBOSE) |
更多模式能力
-
更多元字符:
|
是 “或” 运算符,如Crow|Servo
匹配Crow
或Servo
;^
在默认情况下匹配字符串开头,在re.MULTILINE
模式下还可匹配每行开头;$
匹配行末尾,在re.MULTILINE
模式下还可匹配每行末尾;\A
仅匹配字符串开头;\Z
只匹配字符串尾;\b
是字边界,匹配单词开头或结尾;\B
与\b
相反,匹配不在字边界的位置。 -
分组:用
(
和)
标记分组,分组可捕获匹配文本的索引,组从 0 开始编号,组 0 表示整个正则。如(a)b
,匹配对象的group(0)
返回整个匹配字符串,group(1)
返回第一个分组匹配的字符串。还可使用后向引用,如\b(\w+)\s+\1\b
检测重复单词 。 -
非捕获和命名组:非捕获组
(?:...)
用于表示正则的一部分,但不捕获匹配内容,可在不改变其他组编号的情况下添加新组。命名组(?P<name>...)
通过名称引用组,方便记忆和使用,如re.compile(r'(?P<word>\b\w+\b)')
,匹配对象可用group('word')
获取匹配内容,还可通过groupdict()
将命名分组提取为字典。 -
前视断言:前视断言有肯定型
(?=…)
和否定型(?!…)
。肯定型前视断言内部表达式匹配则成功,但引擎不向前推进;否定型前视断言内部表达式不匹配则成功。如匹配扩展名不是bat
的文件名,可用.*[.](?!bat$)[^.]*$
。
修改字符串
-
分割字符串:模式对象的
split()
方法根据正则匹配拆分字符串,返回片段列表,可限制拆分次数。若正则中有捕获括号,括号内的内容也会作为列表一部分返回。re.split()
是模块级函数,功能类似 。 -
搜索和替换:
sub()
方法用指定字符串替换匹配的子字符串,可指定替换次数;subn()
方法返回替换后的字符串和替换次数。replacement
可以是字符串或函数,若是字符串,其中的反斜杠转义会被处理,后向引用会替换为相应组匹配的子字符串;若是函数,每次匹配会调用该函数,函数根据匹配对象计算替换字符串 。
实际项目应用案例
-
数据清洗项目:在一个文本数据分析项目中,需要从大量的用户评论数据中提取有用信息并清洗数据。评论数据包含各种格式不规范的内容,如包含特殊字符、HTML 标签等。利用正则表达式可去除 HTML 标签,如使用
re.sub(r'<.*?>', '', text)
,其中<.*?>
匹配所有 HTML 标签,将其替换为空字符串。还可以提取特定格式的数据,如提取评论中的邮箱地址,使用re.findall(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', text)
,该正则表达式通过分组和字符类等元字符,精确匹配符合邮箱格式的字符串。 -
日志分析项目:在服务器日志分析场景中,日志文件记录了大量的访问信息。要统计特定 IP 地址的访问次数,可以用
re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', log_text)
提取日志中的 IP 地址,再进行计数统计。若要分析访问的 URL 路径,可使用re.findall(r'GET\s+([^ ]+)', log_text)
,其中GET
是 HTTP 请求方法,([^ ]+)
分组捕获 URL 路径,通过这种方式可以了解用户对不同页面的访问情况,为优化网站提供数据支持。 -
代码检查工具开发:开发一个代码检查工具,用于检查 Python 代码中的函数命名是否符合规范(假设规范为函数名只能包含小写字母、数字和下划线,且不能以数字开头)。可以使用
re.findall(r'\bdef\s+([a-z_][a-z0-9_]*)\s*\(', code_text)
来查找代码中的函数定义,并通过正则表达式判断函数名是否符合规范。若不符合,可提示开发者进行修改,提高代码的规范性和可读性。
常见问题
-
使用字符串方法:处理固定字符串或单个字符类且不使用
re
模块功能时,使用字符串自身的方法(如replace()
、translate()
)可能更高效,应优先考虑。例如,将字符串中的所有空格替换为下划线,使用字符串的replace
方法"hello world".replace(" ", "_")
,代码简洁且执行效率高。但如果涉及复杂的匹配模式,如替换所有连续出现两次及以上的空格为单个下划线,就需要借助正则表达式re.sub(r' {2,}', ' ', "hello world today")
来实现。 -
match () 和 search () 的区别:
match()
只检查正则是否从字符串开头匹配,search()
会扫描整个字符串查找匹配项。不要为了让match()
实现search()
的功能而在正则前添加.*
,应直接使用search()
。例如,在查找字符串"this is a test"
中是否包含"is"
时,使用re.match('is', "this is a test")
会返回None
,因为match
从字符串开头匹配,而re.search('is', "this is a test")
能正确找到匹配项并返回相应的匹配对象。 -
贪婪与非贪婪:注意重复匹配的贪婪和非贪婪模式,处理 HTML 标签等需要精确匹配的场景时,使用非贪婪限定符避免匹配过多内容。比如匹配 HTML 段落标签
<p>content</p>
,若使用贪婪模式re.match('<p>.*</p>', "<p>first</p><p>second</p>")
,会匹配整个包含两个段落的字符串;而使用非贪婪模式re.match('<p>.*?</p>', "<p>first</p><p>second</p>")
,则只会匹配第一个段落标签及其内容。 -
使用 re.VERBOSE:复杂正则表达式可读性差,使用
re.VERBOSE
标志可通过忽略空格和添加注释来提高可读性。例如,匹配 IP 地址的正则表达式r'((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
,使用re.VERBOSE
后可以写成:
import re
ip_pattern = re.compile(r"""(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # 匹配0 - 255之间的数字\. # 匹配点号(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # 匹配0 - 255之间的数字\. # 匹配点号(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # 匹配0 - 255之间的数字\. # 匹配点号(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # 匹配0 - 255之间的数字
""", re.VERBOSE)
这样更易理解和维护。
总结
本文全面且深入地介绍了 Python 中re
模块的正则表达式用法。从基础概念出发,详细讲解了匹配字符、元字符的各种用法;深入探讨了重复匹配的机制,包括贪婪与非贪婪模式;全面阐述了在 Python 中使用正则表达式的具体方式,涵盖编译、匹配、修改字符串等操作;还介绍了更多模式能力,如分组、断言等高级特性;通过数据清洗、日志分析、代码检查等实际项目案例,展示了正则表达式在真实场景中的强大应用;最后总结了常见问题及解决方法。正则表达式功能强大,但使用时需谨慎,开发者应在实践中不断积累经验,根据具体需求灵活运用各种技巧,从而高效地完成文本处理任务。
TAG:Python;正则表达式;re 模块;字符串处理;元字符;重复匹配;分组;前视断言;数据清洗;日志分析;代码检查
相关学习资源
-
官方文档:Python 官方 re 模块文档,详细介绍了
re
模块的函数、类和正则表达式语法,是深入学习的基础资料。 -
在线教程:RegexOne,提供了丰富的交互式正则表达式练习,通过实际操作帮助初学者快速上手,加深对正则表达式的理解。
-
书籍:《精通正则表达式》,深入讲解正则表达式的原理、高级应用和优化技巧,适合想要进一步提升正则表达式运用能力的读者。