5
25
2013
8

给 Python 的正则匹配限制执行时间

本文来自依云's Blog,转载请注明。

看到这个标题,你也许会想,这个需要限制么?不是很快就出来结果了么?

感谢 Just Great Software,虽然我没买它的产品,但是其说明书(可免费下载)中的正则教程详细地论述了这点。所以我在自己的 xmpptalk 机器人中一直不敢接受用户输入的正则表达式。引述其中的一句话:「People with little regex experience have surprising skill at coming up with exponentially complex regular expressions.」(不太懂正则的人经常能令人惊奇地写出指数级复杂度的正则。)

但很不幸,我从这里抄到的匹配网址的正则就有这种问题。在将其的修改版给我的 XMPP 机器人 Lisa 使用后,Lisa 两次被含有括号的链接搞到没响应……

所以,如果要使用用户输入的正则,我必须限制其匹配时间。方法也很简单——使用信号就可以了。当 Python 在匹配正则时如果收到信号,会转而调用信号处理器,然后再接着匹配。如果信号处理器抛出了异常,那么此异常会传播到调用正则匹配的地方,从而中断匹配操作。

示例如下:

#!/usr/bin/env python3

import re
# import regex as re
import signal

def timed_out(b, c):
  print('alarmed')
  raise RuntimeError()

signal.signal(signal.SIGALRM, timed_out)
signal.setitimer(signal.ITIMER_REAL, 0.1, 0)
s = '<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>'
r = re.compile(r'''(?:<(?:[^<>]+)*>)+b''')
try:
  r.findall(s)
except RuntimeError:
  print('time exceeded')

被注释掉的那句是调用mrab-regex-hg这个正则引擎的;它不会回溯时出这种问题。

优化下代码,写成方便使用(使用了TimeoutError,所以适用于 Python 3.3+):

import contextlib
import signal

@contextlib.contextmanager
def execution_timeout(timeout):
  def timed_out(signum, sigframe):
    raise TimeoutError

  old_hdl = signal.signal(signal.SIGALRM, timed_out)
  old_itimer = signal.setitimer(signal.ITIMER_REAL, timeout, 0)
  yield
  signal.setitimer(signal.ITIMER_REAL, *old_itimer)
  signal.signal(signal.SIGALRM, old_hdl)
Category: python | Tags: linux python 正则表达式 | Read Count: 17208
Rykka 说:
May 26, 2013 11:21:19 AM

为什么这个正则会超时, (r'''(?:<(?:[^<>]+)*>)+''') 就不会超时呢??

Rykka 说:
May 26, 2013 11:23:55 AM

哦,因为匹配的都被消耗掉了,没匹配会不断尝试

Avatar_small
Jacky Liu 说:
May 26, 2013 06:03:18 PM

“People with little regex experience have surprising skill at coming up with exponentially complex regular expressions.” ---- 太对了,尤其对我来说。我只稍微试了几下,就写出一个能在小文档里跑到死的 Vim pattern,而且超简单。给你: \(\(\(\w\+\)\_.\{-}\3\)\_.\{-}\2\)\_.\{-}\1

风间星魂 说:
May 27, 2013 09:53:51 AM

信号处理函数里抛异常?
老实说信号处理函数能安全干的事非常少。。

Avatar_small
依云 说:
May 27, 2013 11:31:19 AM

那是 C。Python 里注册的信号处理函数是在实际的信号处理完毕之后调用的。

cc 说:
Jun 02, 2013 12:59:01 AM

多线程中无法使用Signal怎么解决

Avatar_small
依云 说:
Jun 02, 2013 01:10:36 AM

我不用多线程。
你要不 fork 个进程来匹配?


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com