在Python正则表达式中,\B+、[\B]+和[^\b]+有什么区别?

3

在回答一个SO问题时,我遇到了一个无法理解的问题。我创建了一个简化的示例来说明这个问题:

情景描述:

我正在测试一个字符串中两个标记(不是随机的英文单词!)之间是否至少有一定距离。在这个例子中,我们有一个动物列表,我们想要确保绵羊和狼之间至少有三只其他动物(否则就会有麻烦)。以下代码可以工作:

import re

safe_distance = re.compile(r"sheep (\b[^\b]+\b ){3,}wolf")

animal_arrangements = [
    "dog sheep hen wolf fox cat ox",  # one between
    "dog sheep hen fox wolf cat ox",  # two between
    "dog sheep hen fox cat wolf ox",  # three between
    "dog sheep hen fox cat ox wolf"   # four between
]

for i, animal_arrangement in enumerate(animal_arrangements):

    if safe_distance.search(animal_arrangement):
        print(i + 1, "All is peaceful.")
    else:
        print(i + 1, "Sheep and wolf too close!")

问题:

在上述模式中,使用:

[^\b]+  # works fine
\B+     # causes a regex compilation error "nothing to repeat"
[\B]+   # runs but produces wrong answers
\w+     # yes, this does work, probably best, but not related to my question

为什么会有这种差异?我不需要更好的解决方案来解决羊/狼放置问题——我只是想理解为什么这三种一个或多个非单词边界模式的变体会产生不同的结果。

我知道\b像锚一样,它不代表一个字符,而且这个模式考虑不周(与使用`\w'相比),但是为什么会有这种差异呢?

2个回答

3
  • \B+会导致错误,因为重复边界没有意义 - 一个边界和两个边界是相同的。很可能是您错误地这样做了,因此错误是有道理的。
  • [\B]+是完全不同的东西。(大多数)转义序列在字符类中不起作用,这就是为什么这是一个字符集,匹配字符\B,因此显然可以重复使用。

测试结果不错,[\B]+ 可以匹配重复的 B -- 不过我无法让它匹配 's,所以你分析的那部分可能不正确。 - cdlane
扩展你的推理,[^\b]+ 应该匹配一个或者没有 'b',即与 [^b]+ 相同,但实际情况并非如此,因为 re.search(r"[^\b]+", "bbb")re.search(r"[^b]+", "bbb") 产生不同的结果。 - cdlane
1
@cdlane中的\b(方括号内)应该是实际的退格符号。 - Sebastian Proske
你的\B+解释与\b+一致,很有道理。两个中有两个正确,加1分! - cdlane
谢谢 @SebastianProske,那样就可以了,你成功地掌握了第三个变量!+1 - cdlane

3
\B+模式会导致“nothing to repeat”错误,这是在尝试量化零宽断言特殊正则表达式运算符时常见的错误。其中任何一个 - (*)|*\b+\B+ - 都会导致此错误。重复零宽断言是没有意义的,因为它不消耗任何字符,正则表达式索引仍停留在同一位置。请注意,a{1,2}+f*+(Python re不支持的占有型量词)会导致另一个类似的错误 - “multiple repeat”。
现在,\b\B不能在字符类中使用。请参阅re Python reference
注意,\b用于表示单词边界,并且仅在字符类内部表示“退格”。 ... 在字符范围内,\b表示退格字符,以与Python字符串文字兼容。
此外,FYI, 在字符类中,\B\A\Z和后向引用\1都不能使用。它们失去了它们特殊的正则表达式含义,被视为Python认为正确的任何内容。实际上,由于Python将无效的转义序列解析为\+字符,所以[\B]只匹配B字符,因为\转义了一个文字符号,并且该符号被视为这样匹配
print(re.findall(r'[\B]+', "BBB \\Bash"))

仅输出['BBB', 'B'].

r"[^\b]+"只匹配所有不是退格符的字符:

print(re.findall(r'[^\b]+', "bbb \\bash\baaa"))

输出结果为['bbb \\bash', 'aaa']


如果您不介意删除关于\w等的介绍性部分,我很乐意将您的答案标记为接受,因为它们的可取性以及我的愿望是回答不要试图改进羊/狼算法已经在我的问题中得到了解决。 您对[\B]+和变体问题的讨论非常出色。 我隐藏的目的是展示人们可以错误地从\D+[\D]+[^\d]+中推断出与我使用的`\B+'的结果相同的东西,并且会出现错误(包括一个看起来像我想要的但实际上并不是,而且很容易隐藏在“工作”代码中的情况)。 - cdlane
我删除了开头的建议。实际上,字符类内的零宽度简写失去了其特殊含义。 [\d][\s][\w]是字符类内的消耗性简写,并且它们在Python(以及大多数其他)字符类中得到支持。 - Wiktor Stribiżew
在Java和.NET中,[\B][\A]等被视为语法错误,就像不存在的转义字符\j一样。我认为这是一个更好的方法;任何有意使用[\B]的人可能需要一些补救反馈。我也希望看到[\b][^\b]被视为错误。有人曾经需要特别匹配或排除退格符吗?如果我曾经看到过这种情况,那就是本末倒置! - Alan Moore
1
@AlanMoore:Python 的 re 模块相当特定,很多事情在其他正则表达式引擎中并不适用,例如,在 Python 中 \Z 与 Java 和 .NET 正则表达式中的 \z 是相同的。在 .NET 中可以使用 [\w-\s],但是在 Python 中 - 将被视为范围运算符(错误:bad character range)。我认为 Python 的作者们想让 re 模块比它需要的更聪明 :) - Wiktor Stribiżew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接