我正在为Trac创建一个宏,它所做的一件事就是呈现一些wiki文本,它可以反过来使用同一个宏。检测无限递归
如果使用相同的参数调用内部宏(即呈现相同位置的维基文本),这可以引起无限递归。我想通过检查调用堆栈和中断递归来阻止用户像这样拍摄自己的脚,如果扩展宏的函数已经用完全相同的一组参数调用。
我一直在寻找的inspect module,这无疑好像要走的路,但还是无法弄清楚如何发现堆栈中的前一个函数的参数值。我怎样才能做到这一点?
我正在为Trac创建一个宏,它所做的一件事就是呈现一些wiki文本,它可以反过来使用同一个宏。检测无限递归
如果使用相同的参数调用内部宏(即呈现相同位置的维基文本),这可以引起无限递归。我想通过检查调用堆栈和中断递归来阻止用户像这样拍摄自己的脚,如果扩展宏的函数已经用完全相同的一组参数调用。
我一直在寻找的inspect module,这无疑好像要走的路,但还是无法弄清楚如何发现堆栈中的前一个函数的参数值。我怎样才能做到这一点?
捕获递归的例外是更好的方法,但你也可以在你想“保护”的功能添加装饰:
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
当发生递归错误时,比在运行时发现递归错误更容易。
如果这不是一个选项,那么在渲染之前分析模板也可能是一种前进的方法。
提供的代码并不需要太长的时间来进行堆栈溢出。如果是这样,这可能是不切实际的。 (+1) – NPE 2013-04-11 17:58:13
@NPE:当然,如果这是一个非常缓慢的过程。但是渲染一个模板不应该。 – 2013-04-11 17:58:49
这是我第一次尝试,但由于某些原因,我仍然无法理解,发生这种情况时不会出现异常。当我通过{{tracd}}运行trac时会引发这个问题,这个过程就会停止。没有调试信息。没有例外。没有。 – 2013-04-11 18:00:06
同样简单的是通过字典来跟踪使用的参数,并在开始时检查参数是否已经尝试过。
并不是那么简单,因为我使用的是trac的API。为了添加宏,我需要覆盖''IWikiMacroProvider''提供的方法''expand_macro(self,formatter,name,content)''。这不是直接调用自己的方法,而是在我调用Chrome浏览器(self.env).render_template(...)的时候的某个地方。 – 2013-04-11 18:04:04
我想@ MartijnPieters的答案接近你的建议。 – 2013-04-11 18:18:12
太好了,谢谢!对于未来的读者,[本文](http://stackoverflow.com/a/1894371/684253)提供了一个很好的解释和“threading.local”工作方式的例子。 – 2013-04-12 10:13:38