我想让我的用户使用正则表达式来表示某些功能。我很好奇将用户输入传递给re.compile()的含义是什么。我假设用户没有办法给我一个可以让他们执行任意代码的字符串。我想到的危险是:
1.解决方案很简单:捕获异常。我不确定是否有一个很好的解决方案2.也许只是限制正则表达式的长度会起作用。
还有什么我需要担心的吗?
答案 0 :(得分:20)
我已经开发了一个程序,允许用户输入他们自己的正则表达式而你是对的 - 他们可以(并且确实)输入可能需要很长时间才能完成的正则表达式 - 有时长于宇宙的生命周期。更糟糕的是,处理正则表达式Python时会保留GIL,因此它不仅会挂起运行正则表达式的线程,而且会挂起整个程序。
限制正则表达式的长度是行不通的,因为问题是回溯。例如,在长度为N且不包含“x”的字符串上匹配正则表达式r"(\S+)+x"
将回溯2 ** N次。在我的系统上,这需要大约一秒钟来匹配"a"*21
,并且每个附加字符的时间加倍,所以100个字符的字符串需要大约19167393131891000年完成(这是一个估计,我还没有计时)
有关更多信息,请阅读O'Reilly的书“掌握正则表达式” - 这里有几章关于性能的内容。
修改强> 为了解决这个问题,我们编写了一个正则表达式分析函数,试图捕获并拒绝一些更明显的退化情况,但是不可能得到所有这些函数。
我们看到的另一件事是修补re模块,如果它回溯太多次,就会引发异常。这是可能的,但需要更改Python C源代码并重新编译,因此不可移植。我们还提交了一个补丁,以便在匹配python字符串时释放GIL,但我不认为它被接受到核心(python只保存GIL,因为正则表达式可以针对可变缓冲区运行)。
答案 1 :(得分:6)
对于临时用户来说,为他们提供子集语言要简单得多。例如,fnmatch中的shell的全局规则。 SQL LIKE条件规则是另一个例子。
将用户的语言翻译成正确的正则表达式,以便在运行时执行。
答案 2 :(得分:4)
编译正则表达式应该是相当安全的。虽然它编写的内容并不是严格意义上的NFA(反向引用意味着它不是那么干净),但它仍然可以直接编译成。
现在关于性能特征,这完全是另一个问题。由于回溯,即使是小的正则表达式也可能具有指数时间特征。定义特定的某个子集可能更好,并且只支持您自己翻译的非常有限的表达式。
如果您真的想支持常规正则表达式,则必须信任您的用户(有时是一个选项)或限制使用的空间和时间。我相信所使用的空间仅由正则表达式的长度决定。
编辑:正如Dave所说,显然全局解释器锁在正则表达式匹配期间被保持,这将使得设置超时更加困难。如果是这种情况,您设置超时的唯一选择是在单独的进程中运行匹配。虽然不完全理想,但它是可行的。我完全忘了multiprocessing
。共享对象的兴趣点是this section。如果你真的需要硬约束,那么就可以选择单独的流程了。
答案 3 :(得分:1)
除非需要重用许多不同的正则表达式,否则不必使用compile()。该模块已经缓存了最后的表达式。
如果允许用户输入任何正则表达式,则第2点(执行时)可能非常困难。您可以创建一个包含少量字符的复杂正则表达式,例如着名的(x+x+)+y
字符。我认为这是一个尚未以一般方式解决的问题。
解决方法可能是启动另一个线程并对其进行监视,如果超过允许的时间,则终止该线程并返回错误。
答案 4 :(得分:0)
我真的不认为只需将代码传递给re.compile就可以执行代码。我理解它的方式,re.compile(或任何语言的任何正则表达式系统)将正则表达式字符串转换为finite automaton(DFA或NFA),尽管名称为'compile',但它与之无关。执行任何代码。
答案 5 :(得分:0)
从技术上讲,您不需要使用re.compile()
对字符串执行正则表达式操作。实际上,如果你只执行一次操作,那么编译方法往往会慢一些,因为与初始编译有关的开销。
如果您担心“编译”这个词,那么请将它们全部放在一起,只需将原始表达式传递给match
,search
等。最后可能会提高代码的性能不管怎么说。