正则表达式中的可选点

Posted

技术标签:

【中文标题】正则表达式中的可选点【英文标题】:Optional dot in regex 【发布时间】:2015-01-10 12:36:12 【问题描述】:

假设我想将Mr.Mr 的所有匹配项替换为Mister

我正在使用以下正则表达式:\bMr(\.)?\b 匹配 Mr. 或仅匹配 Mr。然后,我使用re.sub() method 进行替换。

让我感到困惑的是,它正在用Mister. 替换Mr.。为什么要在末尾保留点.?看起来它与Mr\. 的情况不匹配,而只是Mr

import re
s="a rMr. Nobody Mr. Nobody is Mr Nobody and Mra Nobody."
re.sub(r"\bMr(\.)?\b","Mister", s)

返回:

'a rMr. Nobody Mister. Nobody is Mister Nobody and Mra Nobody.'

我也尝试了以下方法,但也没有运气:

re.sub(r"\b(Mr\.|Mr)\b","Mister", s)

我想要的输出是:

'a rMr. Nobody Mister Nobody is Mister Nobody and Mra Nobody.'
                     ^                              ^
                     no dot            this should be kept as it is

【问题讨论】:

您正在对可选的 '.' 进行分组。字符。 您也可以使用解决方法\bMr\b\.? @hwnd 这是个好方法!我喜欢,谢谢。 @fedorqui 你想在foo:Mr:bar 中匹配Mr 吗? @AvinashRaj : 不太可能出现在我的字符串中。唯一可能的情况是Mr-Nobody,它也应该扩展为Mister。因此,想法是所有MrMr. 都应扩展为Mister,除非它们后面出现字母数字字符。 【参考方案1】:

我认为您想捕获'Mr',后跟'.'单词边界

r"\bMr(?:\.|\b)"

使用中:

>>> import re
>>> re.sub(r"\bMr(?:\.|\b)", "Mister", "a rMr. Nobody Mr. Nobody is Mr Nobody and Mra Nobody.")
'a rMr. Nobody Mister Nobody is Mister Nobody and Mra Nobody.'

【讨论】:

不错!哪些边缘情况会阻止?:?对我来说,\bMr(\.|\b) 工作正常。 @fedorqui 在Mr 之后需要捕获点或单词边界吗? @fedorqui (\.|\b) 还捕获了 Mr 后面的任何内容,例如,您可以在正则表达式后重用它。 ?: 使它不会发生。它对性能和可读性几乎没有任何影响,但如果您重复一个组未知次数,然后需要在该重复组后面捕获一个组,它会很有帮助。使重复组不捕获意味着您提前知道捕获组的索引是什么,并且不必先计算它。 @NateKerkhofs 我会说它对可读性有重大影响,尤其是在这种情况下毫无意义的情况下。 \bMr\b\.?不是更简单吗?【参考方案2】:
re.sub(r"\bMr\.|\bMr\b","Mister", s)

试试这个。你需要在.之后删除\b

输出:a rMr. Nobody Mister Nobody is Mister Nobody and Mra Nobody.'

\bMr(\.)?\b 不起作用的原因是.space 之间没有字边界。

有资格作为单词边界的三个不同位置:

在字符串的第一个字符之前,如果第一个字符是单词字符。 在字符串的最后一个字符之后,如果最后一个字符是单词字符。 字符串中两个字符之间,一个是单词字符,另一个不是单词字符。

【讨论】:

对不起,我的问题不够清楚:Mra 必须保持原样,我不希望它被替换为Mistera。也就是说,我想要Mr.Mr + 字边界。 OP 不想在 Mr.hghggh 中匹配 Mr. 这是一个很好的答案,非常感谢您对我做错了什么的非常有见地的解释,您如何正确地做以及为什么做错。我选择@jonrsharpe 的答案作为已接受的答案,因为它更直接,但绝对赞成这一点。【参考方案3】:

我认为在原始帖子中\b 是造成一些混乱的原因。

来自regex101:

\b 在 a 之间立即匹配,不消耗任何字符 \w 匹配的字符和 \w 不匹配的字符(在任一 顺序)。

\w 匹配任何字母、数字或下划线。

OP 期望 \b 匹配点和它后面的空格之间的边界。但它没有,因为点与\w 不匹配。相反,\b 匹配“先生”文本和点之间的边界。这导致点没有被捕获,这是 OP 所要求的。可以在这里看到:

【讨论】:

有趣!请注意,尽管以 Mr 开头的字符串不会被您当前的解决方案替换。这就是词边界\b的原因。 好点!我是按照这个例子来的。我删除了解决方案并留下了对问题的解释。【参考方案4】:
>>> s="a rMr. Nobody Mr. Nobody is Mr Nobody and Mra Nobody."
>>> re.sub(r'\b(Mr[\.\s]\s*)',r'Mister ',s)
'a rMr. Nobody Mister Nobody is Mister Nobody and Mra Nobody.'

【讨论】:

这可行,但它会将“先生”之后的多个空格替换为一个空格。请参阅regex101.com/r/sC9nG6/4 为避免此问题,您可以改用re.sub(r'\b(Mr(\.)?(\s+))',r'Mister\3',s)。见regex101.com/r/sC9nG6/5【参考方案5】:
re.sub(r'\bMr[\s\.]', 'Mister ', s)

如果这是 Code Golf,我会赢吗?

【讨论】:

@fedorqui - 谢谢 - 这当然是我的本意。 问题是,这个解决方案将Mr.␣Nobody 转换为Mister␣␣Nobody。也就是说,它增加了一个额外的空间。见 regex101.com/r/dE1yF6/1【参考方案6】:

@jonsharpe 的回答有效,但这个有点简单:\bMr(\.|\b)

http://regex101.com/r/sC9nG6/2

【讨论】:

(?:...) 是为了避免不必要的捕获。 但它提供了所需的输出。 当然可以,但是这个答案有什么改进呢?另一个答案明确地使组不捕获以稍微减少开销。 这个答案中有更少的符号供读者解析。来自 Python 之禅:“可读性很重要。”

以上是关于正则表达式中的可选点的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式省略 UPN 或 displayName 中的可选前缀

使用正则表达式匹配日志文件行时的可选字段

在正则表达式的可选部分中捕获的组

正则表达式捕获输入块中间的可选组

正则表达式 C# - 中间的可选组

带有字母、数字、任意顺序的可选特殊字符的正则表达式单词