前瞻和非捕获正则表达式

6 投票
1 回答
6598 浏览
提问于 2025-04-16 23:28

我正在尝试匹配电子邮件地址中@符号前面的部分:

LOCAL_RE_NOTQUOTED = """
((
\w         # alphanumeric and _
| [!#$%&'*+-/=?^_`{|}~]          # special chars, but no dot at beginning
)
(
\w         # alphanumeric and _
| [!#$%&'*+-/=?^_`{|}~]          # special characters
| ([.](?![.])) # negative lookahead to avoid pairs of dots. 
)*)
(?<!\.)(?:@)           # no end with dot before @
"""

测试用:

re.match(LOCAL_RE_NOTQUOTED, "a.a..a@", re.VERBOSE).group()

结果是:

'a.a..a@'

为什么输出中会显示@,尽管我使用了不捕获组(?:@)呢?

测试用:

 re.match(LOCAL_RE_NOTQUOTED, "a.a..a@", re.VERBOSE).groups()

结果是:

('a.a..a', 'a', 'a', None)

为什么这个正则表达式没有拒绝包含一对点'..'的字符串呢?

1 个回答

13

你把不捕获组 (?:...) 和前瞻断言 (?=...)搞混了。

不捕获组是参与匹配的,也就是说它们会出现在 match.group() 里面,这个包含了整体的匹配结果,只是它们不会生成可以后续使用的引用(比如 $1 之类的)。

第二个问题(为什么会匹配到两个点?)就有点复杂了。这是因为你的正则表达式有个错误。你看,当你写的时候(为了说明问题,简化了一下)

[+-/]

你写的是“匹配一个在 +/ 之间的字符,而在 ASCII 码中,点正好在它们之间(ASCII 43-47: +,-./)。所以,第一个字符类匹配到了点,而前瞻断言根本没有被执行到。你需要把连字符放在字符类的最后面,这样它才会被当作一个普通的连字符来处理:

((
\w         # alphanumeric and _
| [!#$%&'*+/=?^_`{|}~-]          # special chars, but no dot at beginning
)
(
\w         # alphanumeric and _
| [!#$%&'*+/=?^_`{|}~-]          # special characters
| ([.](?![.])) # negative lookahead to avoid pairs of dots. 
)*)
(?<!\.)(?=@)           # no end with dot before @

当然,如果你想用这个逻辑,可以稍微简化一下:

^(?!\.)                   # no dot at the beginning
(?:
[\w!#$%&'*+/=?^_`{|}~-]   # alnums or special characters except dot
| (\.(?![.@]))            # or dot unless it's before a dot or @ 
)*
(?=@)                     # end before @

撰写回答