Python re中的贪婪与非贪婪匹配
请帮我看看,这是不是Python(2.6.5)里的一个bug,还是我写正则表达式的能力有问题,或者是我对模式匹配的理解不够。
(我知道一个可能的答案是“升级你的Python”。)
我正在尝试解析一个Yubikey令牌,允许一些可选的额外内容。
当我用这个正则表达式去匹配一个没有任何可选内容的令牌(也就是说,只包含匹配两个捕获组的内容)时,匹配失败:
r'^\t?[^a-z0-9]?([cbdefghijklnrtuv1-8]{0,32})\t?([cbdefghijklnrtuv1-8]{32})\t?\r?\n?$'
但是,如果我把第一个组改成非贪婪模式:
r'^\t?[^a-z0-9]?([cbdefghijklnrtuv1-8]{0,32}?)\t?([cbdefghijklnrtuv1-8]{32})\t?\r?\n?$'
那么匹配就成功了。
所以,好的,它是有效的,但我本以为这两个正则表达式的最终结果之间唯一的区别应该是性能。
Expresso和Regex Coach都喜欢这两种模式。
我错过了什么呢?
这里是我正在测试的两个字符串。
没有可选内容(那些可能会失败的):
"vvbrentlnccnhgfgrtetilbvckjcegblehfvbihrdcui"
有可选内容(到目前为止没有失败;实际的制表符在这里显示为“_”):
"_!_8R5Gkruvfgheufhcnhllchgrfiutujfh_"
"_!1U4Knivdgvkfthrd_brvejhudrdnbunellrjjkkccfnggbdng_"
我尝试使用Alex Martelli的建议来重现这个问题,但在原生Python环境中并没有失败,所以我打算重新检查我的代码(我实际上是在修改yubikey-python);我会在一两天内再反馈情况。
对大家说声抱歉。我无法重现这个问题。当时,我是通过getpass
读取输入的;我怀疑是意外的外部按键干扰了。
我打算关闭这个问题。如果有人给这个问题投了赞成票,想要撤回,那也是可以理解的。
非常抱歉。
2 个回答
你说得对:把贪婪模式换成非贪婪模式,应该不会让正则表达式失效。这样做可能会改变正则表达式匹配的速度(或者不匹配的速度)、匹配的内容有多少,以及哪些部分被捕获到哪个组里,仅此而已。
(下面的“解决方案”不适用,不过问题中并没有说明要进行不区分大小写的匹配,所以我就不提了。)
你的问题在于,带有可选内容的字符串中也有大写字母,而你的正则表达式只允许小写字母。只需在正则表达式前面加上(?i)
,这样就可以正常工作了。
我建议使用 yubikey-python 来让Python和yubikey进行交互,不过这只是个附带的(而且是非常实际的问题);-).
理论上来说,选择贪婪匹配和非贪婪匹配不应该导致正则表达式(RE)在某种情况下匹配成功而在另一种情况下失败——它只会影响匹配到的内容(还有你提到的性能),而不是匹配是否成功,因为正则表达式应该能够回溯来处理这个问题。
问题是,我无法重现这个问题——我手头没有yubikey,而在这个文件中的测试显示这两个正则表达式的匹配和不匹配行为没有区别。
你能不能提供几个失败的例子(一个匹配而另一个不匹配),最好是编辑你的问题,这样我可以重现这个问题并尝试找出最小的原因?听起来可能是正则表达式的bug,但没有可重现的案例我无法检查是否已经修复、是否已经报告过,或者其他情况。谢谢!
编辑:提问者现在已经发布了一个失败的例子,但我仍然无法重现:
$ py26
Python 2.6.5 (r265:79359, Mar 24 2010, 01:32:55)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> r1 = re.compile(r'^\t?[^a-z0-9]?([cbdefghijklnrtuv1-8]{0,32})\t?([cbdefghijklnrtuv1-8]{32})\t?\r?\n?$')
>>> r2 = re.compile(r'^\t?[^a-z0-9]?([cbdefghijklnrtuv1-8]{0,32}?)\t?([cbdefghijklnrtuv1-8]{32})\t?\r?\n?$'
... )
>>> nox="vvbrentlnccnhgfgrtetilbvckjcegblehfvbihrdcui"
>>> r1.match(nox)
<_sre.SRE_Match object at 0xcc458>
>>> r2.match(nox)
<_sre.SRE_Match object at 0xcc920>
>>>
也就是说,在这两种情况下匹配都成功,正如应该的那样——而且这正是提问者使用的2.6.5版本的Python。提问者,请在你的平台上展示这组简单命令的结果,并告诉我们你的平台是什么,因为这看起来像是一个奇怪的依赖平台的bug……谢谢!