1.3.8 前向或后向
很多情况下,只有当模式中其他部分也匹配时才会匹配模式的某个部分。例如,在email解析表达式中,尖括号被标志为可选。在实际中,尖括号应当是成对的,所以只有当两个尖括号都出现或者都不出现时表达式才能匹配。这个修改版本的表达式使用一个肯定前向(positive look-ahead)断言来匹配尖括号。前向断言语法为(?=pattern)。
import re
address = re.compile(
'''
# A name is made up of letters, and may include"."
# for title abbreviaations and middle initials.
((?P<name>
([\w.,]+\s+)*[\w.,]+
)
\s+
) # The name is no longer optional.
# LOOKAHEAD
# Email addresses are wrapped in angle brackets,but only
# if both are present of neither is.
(?= (<.*>$) # Remainder wrapped in angle brackets
|
([^<].*[^>]$) # Remainder *not* wrapped in angle brackets
)
<? # Optional opening angle bracket
# The address itself: username@domain.tld
(?P<email>
[\w\d.+-]+ # Username
@
([\w\d+\.])+ # Domain name prefix
(com|org|edu) # Limit the allowed top-level domains.
)
>? # Optional closing angle bracket
''',
re.VERBOSE)
candidates = [
u'First Last <first.last@example.com>',
u'No Brackets first.last@example.com',
u'Open Bracket <first.last@example.com',
u'Close Bracket first.last@example.com>'
]
for candidate in candidates:
print('Candidate:',candidate)
match = address.search(candidate)
if match:
print(' Name:',match.groupdict()['name'])
print(' Email:',match.groupdict()['email'])
else:
print(' No match')
这个版本的表达式中有很多重要的变化。首先,name部分不再是可选的。这说明,单独的地址将不能匹配,而且还会避免匹配哪些格式不正确的“名/地址”组合。“name”组后面的肯定前向规则断言字符串的余下部分要么包围在一对尖括号中,要么不存在不匹配的尖括号,也就是尖括号要么都出现,要么都不出现。这个前向规则表述为一个组,不过前向组的匹配并不消费任何输入文本,所以这个模式的其余部分会从前向匹配之后的同一位置取字符。
运行结果:
Candidate: First Last first.last@example.com
Name: First Last
Email: first.last@example.com
Candidate: No Brackets first.last@example.com
Name: No Brackets
Email: first.last@example.com
Candidate: Open Bracket <first.last@example.com
No match
Candidate: Close Bracket first.last@example.com>
No match
否定前向(negative look-ahead)断言((?!pattern))要求模式不匹配当前位置后面的文本。例如:email识别模式可以修改为忽略自动系统常用的noreply邮件地址。
import re
address = re.compile(
'''
^
# AN address: username@domain.tld
# Ignore noreply addresses.
(?!noreply@.*$)
[\w\d.+-]+ # Username
@
([\w\d.]+\.)+ # Domain name prefix
(com|org|edu) # Limit the allowed top-level domains.
$
''',
re.VERBOSE)
candidates = [
u'first.last@example.com',
u'noreply@example.com',
]
for candidate in candidates:
print('Candidate:',candidate)
match = address.search(candidate)
if match:
print(' Match:',candidate[match.start():match.end()])
else:
print(' No match')
以noreply开头的地址与这个模式不匹配,因为前向断言失败。
运行结果:
Candidate: first.last@example.com
Match: first.last@example.com
Candidate: noreply@example.com
No match
不用前向检查email地址username部分中的noreply,可以借助语法(?<!pattern)在匹配username之后使用一个否定后向(negative look-behind)断言来改写这个模式。
import re
address = re.compile(
'''
^
# An address: username@domain.tld
[\w\d.+-]+ # Username
# Ignore noreply addresses.
(?<!noreply)
@
([\w\d.]+\.)+ # Domain name prefix
(com|org|edu) # Limit the allowed top-level domains.
$
''',
re.VERBOSE)
candidates = [
u'first.last@example.com',
u'noreply@example',
]
for candidate in candidates:
print('Candidate:',candidate)
match = address.search(candidate)
if match:
print(' Match:',candidate[match.start():match.end()])
else:
print(' No match')
后向匹配与前向匹配的做法稍有不同,其表达式必须使用一个定长的模式。只要字符数固定(没有通配符或区间),后向匹配也允许重复。
运行结果:
Candidate: first.last@example.com
Match: first.last@example.com
Candidate: noreply@example
No match
可以借助语法(?<=pattern)使用可定后向(positive look-behind)断言查找符合某个模式的文本。例如,一下表达式可以查找推特用户名。
import re
twitter = re.compile(
'''
# A twitter handle: @username
(?<=@)
([\w\d_]+) # Username
''',
re.VERBOSE)
text = '''This text includes two Twitter handles.
One for @ThePSF,and one for the author,@doughellmann.
'''
print(text)
for match in twitter.findall(text):
print('Handle:',match)
这个模式会匹配能构建一个推特用户名的字符序列,只要字符序列前面有一个@。
运行结果:
This text includes two Twitter handles.
One for @ThePSF,and one for the author,@doughellmann.
Handle: ThePSF
Handle: doughellmann