Python对正则表达式的支持

2022-02-23 21:33:11

LWN订户已向您提供以下仅限订阅的内容。成千上万的订阅者依靠LWN从Linux和自由软件社区获得最好的消息。如果您喜欢这篇文章,请考虑订阅LWN。感谢您访问LWN。网

正则表达式是计算机语言的一个常见功能,尤其是Ruby、Perl、Python等高级语言,用于进行相当复杂的文本模式匹配。有些语言(包括Perl)将正则表达式合并到语言本身中,而另一些语言则有随languageinstallation附带的类或库。蟒蛇';s的标准库有re模块,它提供了处理正则表达式的工具;然而,正如最近关于python思想邮件的讨论所显示的那样,该模块在最近几次被搁置了。

2月14日,J.B.兰斯顿登上榜单;他提交了一个关于他在使用re时遇到的一个问题的bug,并建议将其添加到邮件列表中。Langston有一个正则表达式(缩写为";regex";或";regexp";)当应用到很少出现的日志消息时,这似乎挂起了他的程序;事实证明,他的正则表达式中有一个缺陷,导致了大量的回溯,实际上,这些回溯从未完成。在bug报告中,他要求提供一种指定超时的方法:我将尝试重写我的正则表达式以解决这个特定问题,但它';shard可以预测每一个可能的输入并设计一个防弹正则表达式,所以类似的东西可以用于拒绝服务攻击(无论是有意还是无意)。在本例中,regex用于自动导入过程,并导致该过程在有人注意到之前备份了许多小时。也许一个解决方案是在正则表达式引擎上添加一个超时选项,这样如果正则表达式执行的时间超过配置的超时时间,它就会放弃并抛出一个异常。

他在帖子中进一步阐述了自己的用例:我的用例是日志解析,我有大量的正则表达式,它们运行在许多不同的日志行上。我有大量的正则表达式,它是';很难确保每个正则表达式都没有潜在的问题,尤其是当病理行为只发生在某些输入上时,这些输入在开发正则表达式时可能没有被激活。此外,由于这些正则表达式正在解析的数据量很大,我决不想让正则表达式的运行时间超过几毫秒,因为如果它超过了几毫秒,这将降低我的日志处理吞吐量。我';我宁愿它只是引发一个异常并继续下一个日志条目。

Jonathan Slenders回答说Python包索引(PyPI)上的regexmodule支持超时。regex是用来替代re的adrop(使用它的";版本0";)以及在使用";版本1";。这些特性包括嵌套集、默认情况下完整的Unicode大小写折叠、在匹配并发性时删除全局解释器锁(GIL)等等。regex主页列出了使用版本1时的一系列差异,包括匹配函数的超时参数。

Tim Peters也提到了regex,虽然不是因为它实现了超时(这是他通过线程才学到的东西),而是因为它是";三级模块";具有更新的正则表达式实现的许多功能。它是";也更难引发指数级的不良事件";。他讨论了正则表达式带来的一些折衷,并向那些努力使用正则表达式的人推荐了经典书籍《掌握正则表达式》。他指出,SNOBOL是20世纪60年代的一种字符串处理语言,它没有正则表达式,尽管它(及其后继者,Icon)能够以更自然的方式进行匹配:天真的正则表达式既笨拙,又容易在许多任务中出现计时错误,这些任务";应该是";很容易表达。例如";现在匹配到下一个出现的';X'". 在斯诺博尔·安迪肯,这';这很琐碎。75%的regexp用户会写"*X";,因为它可能比他们预期的更符合Waaay。另外20%的人会写";*?X";,缺乏理解,这可能会超出_just";下一个";在某些情况下是X。这就剩下5%的幸福作家";[^X]*X";,这最终说明了他们的初衷。

那些不精通正则表达式语法的人可能会发现其中一些有点令人费解。虽然本文不能介绍正则表达式,但有无数的网站、书籍和其他资源,我们将尝试为读者提供一些帮助。在正则表达式语法中,"" 表示任何单个字符,添加"*" 表示匹配上一个术语的零次或多次出现,因此"*" 匹配任何字符串,而"*X";匹配任何字符串,最大值为#34;X";。但是,正如下面的例子所示,这可能不是程序员的想法:

从re返回的对象中的匹配值。search()显示它一直匹配到第二次出现";X";在字符串中。那是因为"*" 接线员是";贪婪"-它尽可能地匹配。彼得斯和#39;s消息使用非贪婪量词#34" 为了更接近要求的结果:

5%的病例和#34;[^X]*X";,使用否定字符集&34;[^X]";,也就是说,除了";X";。因此,正则表达式可以理解为";匹配任何不是'的字符;X';,其次是';X'",但这可能不是第一个想到的。史蒂文·D';阿普拉诺很惊讶";*?X";可能超过下一个";X";,但Chris Angelico指出,不贪婪并不意味着它只会与下一个";X";:Nongreedy的意思是';I’我更喜欢下一个X,但它必须对其他人开放。bbb和ccc之间的X赢了#39;结果不匹配,所以?必须捕捉更多。

子线程扩展了一种方式,深入研究了匹配的深层次方面";直到下一个&39;字符串'", 这显示了伴随着错误的#34;解决方案";。可以看出,正则表达式是一种强大的机制,但它们很复杂,可以不恰当地使用,并且容易出现各种各样的错误;它们也很难完全测试,遇到问题时也很难调试。但如今它们已经无处不在';这是计算机领域。彼得斯说:至于为什么regexps占上风,牵引力!它们是有用的工具,而且u开始的时候非常简单,具有小、优雅和高效的实现功能,包括爬行和#34;快点!快点!快点"现在把实现更多地变成了无底洞;-)

Matthew Barnett(";MRAB";),regex的开发人员同意这一评估:regex一开始很简单,所以只需要少数元字符,其余字符被视为文本。随着新功能的增加,现有的元字符被以新的方式使用,而在那之前,这些方式都是非法的,目的是为了重新兼容。此外,还有多个具有不同(有时只是略有不同)功能和行为的实现。

兰斯顿";非常喜欢阅读";这条线索来自他的帖子,但想看看是否有人支持";用于将timeoutfeature添加到Python re库";。他说他将调查正则表达式,但仍然认为re可以从阻止失控正则表达式的方法中获益。Angelico反对这一想法,建议应该采用线程中探索的其他一些匹配技术" 它会增加普通病例的开销,以便在病态病例周围加上一道屏障,而且';很难有效地定义截止时间"不过,正如他在第一次回复中指出的那样,彼得斯认为不太可能在re的功能上做任何工作:埋没在有趣的讨论中的是我的猜测:不可能。蟒蛇';s实际上是死气沉沉的遗留代码,没有当前的";所有者";。它的委员会几年来几乎没有活动。大多数承诺是由于通用的#34;代码清理";与算法无关的十字军东征。没有人需要对实现有非常重要的了解。

Peters说,组成regex模块的代码最初的目标是在2008年由其原作者JeffreyC成为核心Python的一部分。雅各布斯;这项工作由巴内特挑选并继续进行,最终变成了正则表达式。由于PyPI中存在正则表达式,将re转换为使用它的请求于2021年关闭" 如果有人想把它移到Python stdlib中,我建议先从Python ideaslist开始"

问题是regex有"_标准库中有几十个有价值的功能";,彼得斯说,所以:[N]据我所知,没有一个核心开发人员会将他们有限的时间用于复制正则表达式的一小部分';Python有很多改进';它的传统引擎。事实上";安装正则表达式" 在这一点上是如此不寻常的选择,以至于我不会';我甚至没有时间回顾一个增加了超时的补丁。

Barnett说,他最终决定不将正则表达式代码添加到标准库中,至少部分原因是";这将把修复和添加绑定到Python中#39;s发布周期";。Python以";电池包括"" 但不是核反应堆";,因此,在PyPI中使用正则表达式更有意义,他说。彼得斯认为,regex中的一些功能可能早在2008年就已经出现在核反应堆上,但如今被更普遍地使用:[……]蟒蛇';s re模块被冻结在一个不断后退的过去。没人想做这件事,因为,嗯,";regexalready已经做到了!事实上,它';他已经做了15年了;。

彼得斯还指出,尝试正则表达式可能比兰斯顿意识到的要容易。事实上,由于版本0的兼容性模式,他可能会添加一个简单的导入来让它运行起来:在另一条消息中,彼得斯进一步描述了他的意思:我在这里写的是更多的详细说明,这比他们可能想的要容易:他们不';例如,不必重写它们的IRegexp,或者调用不同的函数或方法名,或者担心它们';我会得到不同的结果。这些软件包与insyntax、语义和API高度兼容,只要你坚持重做的事情。这绝不意味着他们应该坚持re的做法。它';他向他们保证“入门”几乎无关紧要。他们当前的代码应该保持不变,除了改变#34;re";至";regex";。

结果是兰斯顿';s程序alreadyuses regex和#34;通过某种过渡依赖性#34;,但与re相比,他的表现并不令人印象深刻。一个700MB的测试数据集用re在77秒内处理完毕,而用regex处理则需要92秒。彼得斯34岁;有点惊讶";至此,自:";大多数时候,人们报告说regex至少比re";稍微快一些;。regex的性能似乎需要注意,尤其是如果它正在成为Python的事实上的正则表达式模块的话。彼得斯';s对稀土现状的分析令人震惊;它显然永远停留在过去。然而,这对许多人来说可能是足够的,但在Python生态系统中有两个单独的部分,都支持更有限的re子集,这似乎是次优的;大多数增强功能可能只会出现在正则表达式中,因此re越来越落后于最先进的技术。

在标准库中用正则表达式取代re(默认情况下使用0版)似乎很有吸引力,不过巴内特似乎至少对这样做有些谨慎。同样的事情也发生在流行的Requests HTTPmodule上,在2015年的Python语言峰会上,它被认为是标准库的一个可能的补充。得出的结论是,对于不在标准库中的请求来说,它更有意义,因为它比正常的Python发布周期(当时是18个月,但现在是每年一次)移动得更快,尤其是在安全更新方面(对该语言进行更新的频率更高,但不如请求本身移动的速度快)。

34岁;电池包括";多年来,Python的故事一直是其成功的一个主要部分,但它开始以各种方式受到影响。首先,大量的电池正在使Python核心开发人员的维护能力变得紧张。这导致了讨论、Python增强计划(PEP),以及多年来关于删除库中某些部分的进一步讨论。标准库中的大多数模块都是在很久以前添加的,一旦添加了方法,就很难删除它,即使包含它的原因和它的维护人员已经消失。

同时,正则表达式显然是Python程序员使用的东西——因此,尽可能在一个地方为它们提供最佳支持似乎是正确的方法。正如彼得斯所指出的,他们是一个";一种具有超简洁符号的工具,其中正确的表达式几乎无法与线条噪音区分开来,而且打字错误很少被检测为语法错误";。在混合中增加不相容的风险(或不同的性能)可能也不会带来如此的快乐。这一切都有点棘手,但当然不是那么棘手。

另一方面,re最初是由FredrikLundh(不幸地在11月去世)开发的,它不需要为许多不同的用例做什么。那些需要超时、原子组、嵌套字符集、所有格量词和其他高级功能的人可以求助。Barnett似乎热衷于保持与re的兼容性,因此它可能会变成一种情况,比如在请求中,应该推荐标准库的替代品,甚至在Python文档中也是如此。没有明确而明显的#34;对";这里似乎有一个解决方案。

(登录发表评论)

这篇文章和讨论没有按照我预期的方式进行。也许我的思维方式并不常见,但我避免使用";非常规";(在正规语言的理论意义上)不惜一切代价实现正则表达式的功能。当然,断言和反向引用可以简化事情,但在这一点上,你';我们试图用一种相当神秘的语言来实现逻辑,而你';用Python编写这种逻辑可能会更好。我本以为这次讨论会自然而然地涉及指数回溯的问题,以及一个切换到真正的";常规";表达式引擎,如谷歌';这是re2。(Russ Cox对此有很好的背景[1])。可能是因为我对正则表达式匹配的态度,我没有';我没有发现默认设置有任何问题#34;re";模块,并且没有';我甚至不知道"的存在;正则表达式";,对于许多Python用户来说,它也不是事实上的标准——我想我';I’我从来没有觉得你缺少特征。在任何情况下,我都不知道最初的用例是否会由一个真正的";常规";引擎,可能可以选择切换到更复杂(和更慢)的引擎,以满足少数需要非常规功能的情况。

同样,我喜欢";对比度";之间:>;可以看出,正则表达式是一种强大的机制,但它们很复杂,可能使用不当,并且容易出现各种各样的错误;在遇到问题时,它们也很难进行全面测试和调试。[...] 它们是有用的工具,而且u开始时u生活非常简单,具有小、优雅和高效的实现功能,包括爬行和#34;快点!快点!快点" 现在把实现更多地变成了无底洞;-)和:>;那些需要超时、原子组、嵌套字符集、所有格量词和其他高级步兵的人。。。

>;(Russ Cox对此有很好的背景[1])>;[1]: https://swtch.com/~rsc/regexp/regexp1。谢谢你的链接。这无疑是一本启发性的读物。让我想知道,真正需要的是什么;34岁;简单的re";带有"的模块;汤普森NFA";正则表达式引擎。毕竟,6位数的加速应该是值得追求的

我觉得这里真正的问题不是python具体对regex做什么,而是他们能对";标准库是模块去死的地方";情况将regex库中的最新技术导入到cpython存储库中会有一段时间不错,但它没有';I don’我真的没有做任何事情来解决它一开始被忽视的原因。它将有效地排挤一个积极维护的pypi模块,而再次支持一个即将无法维护的标准库模块,就像不断发生的那样。当然是';它不在生态系统中';这是最大的利益。

你没有理由';t两者都有(stdlib和pypi)。例如,Ruby默认提供一些gem,您可以在gem文件中独立升级。这为从stdlib中删除打开了一条路径:推荐至";只需将foo添加到您的gemfile#34;然后在以后的版本中可以将其从附带的电池中移除。他们是在ruby 3 iirc中完成的。

我真的希望Python发行版能与一些管理良好的PyPI包的特定版本捆绑在一起,您可以进一步升级。你应该能够相信这样一个事实";正则表达式";它符合3.9版本发布时冻结的文件,但并非如此;没有未来的未记录功能,或者它仍然有以前没有的bug';没有被发现。AFAICT和#34;请求";没有';自2015年以来,t删除了任何记录在案的功能;另一方面";建造";即使在较短的时间内,情况也没有那么稳定。

我觉得几乎每种语言的包装和依赖性都很糟糕,它们都会以不同的方式变得糟糕(尽管大多数语言也有一些弥补的品质)。在Python的例子中:*一方面,import语句使用了一组相对简单易懂的语义(即";只需在目录结构中粘贴一堆py文件,就可以';完成了!";)。这对于脚本编写非常有用,因为您不需要';I don’我不必为了构建一个完全独立的应用程序而去摆弄像CMake这样的东西。*另一方面,这些语义可能太简单了,因为无法指定";我需要版本X或更高版本";在import语句本身中,也不是模块的实际来源。因此,现在这些信息需要存在于某个元数据中,并通过Pip之类的工具分别进行跟踪和管理。*雪上加霜的是,你可以';t在同一个过程中,同一个模块有两个不同的版本,而不进行各种恶劣的黑客攻击,这些攻击可能会破坏某些东西,也可能不会破坏某些东西,这取决于底层模块的工作方式(例如,它是否检查_name_________;它是否摆弄sys.modules?等等)。*当然,初学者常犯的错误是,不小心将Python脚本命名为stdlib模块(Python更愿意第二次重新导入脚本,而不是使用stdlib模块,然后一切都会中断,因为它可能无法实现stdlib模块的API)。如果一个新的stdlib模块与您现有的一个模块同名,但由于某些原因,似乎没有人使用它,这可能会破坏backcompat

......