正则表达式是一种用于匹配字符串的强大工具。问号是正则表达式中的特殊字符之一,它有以下几种用法。
1. 匹配零次或一次
问号的主要用途之一是匹配零次或一次前面的字符。例如,表达式a?b将匹配字母b、ab和空字符串,但不匹配a和其他任何字符串。如下所示:
import re
pattern = 'a?b'
print(re.search(pattern, 'b')) # 匹配
print(re.search(pattern, 'ab')) # 匹配
print(re.search(pattern, '')) # 匹配
print(re.search(pattern, 'a')) # 不匹配
在这个例子中,a?匹配零次或一次a,因此正则表达式将匹配零个或一个a,后跟一个b。
2. 非贪婪匹配
问号还可以用于进行非贪婪匹配。正则表达式通常是贪婪匹配的,这意味着它们将尝试匹配尽可能多的字符。但是,通过在子表达式后面加上问号,可以将其变为非贪婪匹配。例如,表达式a+?将匹配一个或多个a,但只要匹配尽可能少的a。
import re
text = 'abaaabaaa'
pattern = 'a+?'
print(re.findall(pattern, text)) # ['a', 'a', 'a', 'a', 'a', 'a', 'a']
在这个例子中,正则表达式a+将贪婪地匹配所有的a,而正则表达式a+?只会匹配最少数量的a,因此它将返回每个a。
3. 非捕获分组
问号还可以用于创建非捕获分组。捕获分组是一种允许您将子表达式的结果提取到单独的组中的机制,而非捕获分组则不会将它们捕获。非捕获分组以(?:...)的形式出现,其中...是要匹配的子表达式。例如,表达式(?:a)b将匹配字母b后跟a,但不会将a捕获。
import re
text = 'aba'
pattern1 = '(a)b'
pattern2 = '(?:a)b'
g1 = re.search(pattern1, text)
g2 = re.search(pattern2, text)
print(g1.group(1)) # 输出'a'
print(g2.group(1)) # 报错
在这个例子中,g1是指定的模式的捕获组的搜索结果。我们指定了一个要捕获的组,因此它找到了a并将其返回。然而,g2是非捕获组的搜索结果,它没有指定要捕获的组,因此无法使用group()方法获得任何子组的值。
4. 零宽断言
问号还可以用于创建零宽断言。零宽断言是一种先行或后发断言,它指定了一个匹配必须在另一个匹配之前或之后。问号是这些断言的一部分。有以下四种零宽断言:
(1) (?=...) 肯定先行断言:匹配紧接在某些内容之前的位置。例如,表达式foo(?=bar)匹配后面紧跟着bar的foo,但不匹配不紧跟在bar后面的foo。这称为预测性搜索。
import re
text = 'foobar'
pattern = 'foo(?=bar)'
print(re.findall(pattern, text)) # ['foo']
在这个例子中,正则表达式foo(?=bar)匹配foo,在匹配bar之前停止搜索。
(2) (?!...) 否定先行断言:匹配不在某些内容之前的位置。例如,表达式foo(?!bar)匹配不紧跟在bar后面的foo,但不匹配紧跟在bar后面的foo。这称为负预测性搜索。
import re
text = 'foobaz'
pattern = 'foo(?!bar)'
print(re.findall(pattern, text)) # ['foo']
在这个例子中,正则表达式foo(?!bar)匹配foo,因为bar不紧跟在它后面,但它不匹配foo后跟bar的实例。
(3) (?<=...) 肯定后发断言:匹配紧接在某些内容之后的位置。例如,表达式(?<=foo)bar匹配紧跟在foo后面的bar,但不匹配不紧跟在foo后面的bar。
import re
text = 'foobar'
pattern = '(?<=foo)bar'
print(re.findall(pattern, text)) # ['bar']
在这个例子中,正则表达式(?<=foo)bar匹配bar,在匹配foo之后停止搜索。
(4) (?
import re
text = 'foobaz'
pattern = '(?
print(re.findall(pattern, text)) # ['bar']
在这个例子中,正则表达式(?