>>> from env_helper import info; info()
页面更新时间: 2024-03-29 09:20:56
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

1.5. 正则表达式方法详解

1.5.1. 贪心和非贪心匹配

在字符串 'HaHaHaHaHa' 中, 因为 (Ha){3,5} 可以匹配3个、4个或5个实例, 你可能会想,为什么在前面花括号的例子中, Match 对象的group()调用会返回 'HaHaHaHaHa' , 而不是更短的可能结果。毕竟, 'HaHaHa''HaHaHaHa'也能够 有效地匹配正则表达式 (Ha){3,5}

Python 的正则表达式默认是“贪心”的, 这表示在有二义的情况下, 它们会尽可能匹配最长的字符串。 花括号的“非贪心”版本匹配尽可能最短的字符串, 即在结束的花括号后跟着一个问号。

在交互式环境中输入以下代码, 注意在查找相同字符串时, 花括号的贪心形式和非贪心形式之间的区别:

>>> import re
>>> greedyHaRegex = re.compile(r'(Ha){3,5}')
>>> mo1 = greedyHaRegex.search('HaHaHaHaHa')
>>> print(mo1.group())
HaHaHaHaHa
>>> nongreedyHaRegex = re.compile(r'(Ha){3,5}?')
>>> mo2 = nongreedyHaRegex.search('HaHaHaHaHa')
>>> print(mo2.group())
HaHaHa

请注意,问号在正则表达式中可能有两种含义: 声明非贪心匹配或表示可选的分组。这两种含义是完全无关的。

1.5.2. findall() 方法

除了search方法外,Regex 对象也有一个 fmdall() 方法。 search()将返回一个Match对象, 包含被查找字符串中的“第一次”匹配的文本, 而 findall() 方法将返回一组字符串, 包含被查找字符串中的所有匹配。 为了看看 search() 返回的 Match 对象只 包含第一次出现的匹配文本, 请在交互式环境中输入以下代码:

>>> import re
>>> phoneNumberRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
>>> mo1=phoneNumberRegex.search('Cell:415-555-9999 Work:212-555-0000')
>>> mo1.group()
'415-555-9999'

另一方面, findall() 不是返回一个 Match 对象, 而是返回一个字符串列表,只要在正则表达式中没有分组。 列表中的每个字符串都是一段被查找的文本, 它匹配该正则表达式。在交互式环境中输入以下代码:

>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') #has no groups
>>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
['415-555-9999', '212-555-0000']

如果在正则表达式中有分组, 那么 findall 将返回元组的列表。 每个元组表示一个找到的匹配, 其中的项就是正则表达式中每个分组的匹配字符串。 为了看看 findall() 的效果, 请在交互式环境中输入以下代码 (请注意,被编译的正则表达式现在有括号分组):

>>> phoneNumRegex =re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)') # has groups >>>
>>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
[('415', '555', '9999'), ('212', '555', '0000')]

作为 findall() 方法的返回结果的总结, 请记住下面两点:

1.如果调用在一个没有分组的正则表达式上, 例如 \d\d\d-\d\d\d-\d\d\d\d , 方法 findall() 将返回一个匹配字符串的列表, 例如['415','555','1122'),('212-555-0000']

2.如果调用在一个有分组的正则表达式上,例如 (\d\d\d)-(\d\d\d)-(\d\d\d\d), 方法 findall() 将返回一个字符串的元组的列表 (每个分组对应一个字符串), 例如[(’415', '555', '1122'), ('212', '555', '0000')]

1.5.3. 不区分大小写的匹配

通常,正则表达式用你指定的大小写匹配文本。例如, 下面的正则表达式匹配完全不同的字符串:

>>> import re
>>> regexl = re.compile('RoboCop')
>>> regex2 = re.compile('R0B0C0P')
>>> regex3 = re.compile('robOcop')
>>> regex4 = re.compile('RobocOp')

但是,有时候你只关心匹配字母,不关心他们是小写或大写, 要让正则表达式不区分大小写,可以向 re.compile()传入 re.IGNORECASEre.I ,作为第二个参数。

>>> robocop =re.compile(r'robocop', re.I)
>>> robocop.search('RoboCop is part man, part machine, allcop.').group()
'RoboCop'
>>> robocop.search('ROBOCOP protects the innocent.').group()
'ROBOCOP'
>>> robocop.search('Al, why does your programming book talk about robocop so much?').group()
'robocop'

1.5.4. 管理复杂的正则表达式

如果要匹配的文本模式很简单,正则表达式就很好。 但匹配复杂的文本模式,可能需要长的、费解的正则表达式。 你可以告诉 rexompile(),忽略正则表达式字符串中的 空白符和注释,从而缓解这一点。要实现这种详细模式, 可以向 rexompile() 传入变量 re.VERBOSE ,作为第二个参数。

现在,不必使用这样难以阅读的正则表达式:

>>> import re
>>> phoneRegex = re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4}(\s*(ext|x|ext.)\s*\d{2,5})?)')

你可以将正则表达式放在多行中,并加上注释, 像这样:

>>> phoneRegex = re.compile(r'''(
>>>     (\d{3}|\(\d{3}\))?  # area code
>>>     (\s|-|\.)?    # separator
>>>     \d{3}    # first 3 digits
>>>     (\s|-|\.)   # separator
>>>     \d{4}   # last 4 digits
>>>     (\s*(ext|x|ext.)\s*\d{2,5})?   # extension
>>>     )''',re.VERBOSE)

请注意,前面的例子使用了三重引号 (’’’) , 创建了一个多行字符串。 这样就可以将正则表达式定义放在多行中, 让它更可读。

正则表达式字符串中的注释规则, 与普通的Python代码一样:# 符号和它后面直到行末的内容, 都被忽略。而且,表示正则表达式的多行字符串中, 多余的空白字符也不认为是要匹配的文本模式的一部分。 这让你能够组织正则表达式,让它更可读。

1.5.5. 组合使用 re.IGNOREC ASEre.DOTALLre.VERBOSE

如果你希望在正则表达式中使用 re.VERBOSE 来编写注释,还希望使用 re.IGNORECASE 来忽略大小写,该怎么办?遗憾的是, re.compile() 函数只接受一个值作为它的第二参数。可以使用管道字符(|)将变量组合起来,从而绕过这个限制。管道字符在这里称为“按位或”操作符。

所以,如果希望正则表达式不区分大小写, 并且句点字符匹配换行, 就可以这样构造 re.compile() 调用:

>>> import re
>>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL)

使用第二个参数的全部3个选项,看起来像这样:

>>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL  | re.VERBOSE)

可以向第二个参数传入其他选项,它们不常用,但你也可以在前面的资源中找到有关它们的信息。