2013-04-11 54 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