2013-04-11 85 views
1

我正在为Trac创建一个宏,它所做的一件事就是呈现一些wiki文本,它可以反过来使用同一个宏。检测无限递归

如果使用相同的参数调用内部宏(即呈现相同位置的维基文本),这可以引起无限递归。我想通过检查调用堆栈和中断递归来阻止用户像这样拍摄自己的脚,如果扩展宏的函数已经用完全相同的一组参数调用。

我一直在寻找的inspect module,这无疑好像要走的路,但还是无法弄清楚如何发现堆栈中的前一个函数的参数值。我怎样才能做到这一点?

回答

5

捕获递归的例外是更好的方法,但你也可以在你想“保护”的功能添加装饰:

from functools import wraps 
from threading import local 

def recursion_detector(func): 
    func._thread_locals = local() 

    @wraps(func) 
    def wrapper(*args, **kwargs): 
     params = tuple(args) + tuple(kwargs.items()) 

     if not hasattr(func._thread_locals, 'seen'): 
      func._thread_locals.seen = set() 
     if params in func._thread_locals.seen: 
      raise RuntimeError('Already called this function with the same arguments') 

     func._thread_locals.seen.add(params) 
     try: 
      res = func(*args, **kwargs) 
     finally: 
      func._thread_locals.seen.remove(params) 

     return res 

    return wrapper 

然后应用装饰给宏渲染功能。

一个简单的演示:

>>> @recursion_detector 
... def foo(bar): 
...  return foo(not bar) 
... 
>>> foo(True) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 10, in wrapper 
    File "<stdin>", line 3, in foo 
    File "<stdin>", line 10, in wrapper 
    File "<stdin>", line 3, in foo 
    File "<stdin>", line 7, in wrapper 
RuntimeError: Already called this function with the same arguments 
>>> foo(False) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 10, in wrapper 
    File "<stdin>", line 3, in foo 
    File "<stdin>", line 10, in wrapper 
    File "<stdin>", line 3, in foo 
    File "<stdin>", line 7, in wrapper 
RuntimeError: Already called this function with the same arguments 
+0

太好了,谢谢!对于未来的读者,[本文](http://stackoverflow.com/a/1894371/684253)提供了一个很好的解释和“threading.local”工作方式的例子。 – 2013-04-12 10:13:38

4

当发生递归错误时,比在运行时发现递归错误更容易。

如果这不是一个选项,那么在渲染之前分析模板也可能是一种前进的方法。

+2

提供的代码并不需要太长的时间来进行堆栈溢出。如果是这样,这可能是不切实际的。 (+1) – NPE 2013-04-11 17:58:13

+0

@NPE:当然,如果这是一个非常缓慢的过程。但是渲染一个模板不应该。 – 2013-04-11 17:58:49

+0

这是我第一次尝试,但由于某些原因,我仍然无法理解,发生这种情况时不会出现异常。当我通过{{tracd}}运行trac时会引发这个问题,这个过程就会停止。没有调试信息。没有例外。没有。 – 2013-04-11 18:00:06

0

同样简单的是通过字典来跟踪使用的参数,并在开始时检查参数是否已经尝试过。

+0

并不是那么简单,因为我使用的是trac的API。为了添加宏,我需要覆盖''IWikiMacroProvider''提供的方法''expand_macro(self,formatter,name,content)''。这不是直接调用自己的方法,而是在我调用Chrome浏览器(self.env).render_template(...)的时候的某个地方。 – 2013-04-11 18:04:04

+0

我想@ MartijnPieters的答案接近你的建议。 – 2013-04-11 18:18:12