我正在为Trac创建一个宏,它所做的一件事是渲染一些 wiki 文本,而这些文本又可以使用相同的宏。
如果使用相同的参数调用内部宏(即呈现相同的 wiki 文本位),这可能会导致无限递归。我想通过检查调用堆栈并在扩展宏的函数已经使用完全相同的参数集调用时中断递归来尝试阻止用户像这样射击自己的脚。
我一直在查看检查模块,这绝对是要走的路,但仍然无法弄清楚如何发现堆栈上前一个函数的参数值。我怎样才能做到这一点?
捕获递归异常是更好的方法,但您也可以在要“保护”的函数上添加装饰器:
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
在递归错误发生时捕获它比在运行时尝试在它发生之前捕获它更容易。
如果这不是一个选项,那么在渲染之前分析模板也可能是一种前进的方式。
同样简单的是传递字典来跟踪使用的参数,并在开始时检查参数是否已经尝试过。