Python中關於Timeout有另一種用起來更簡便的方法,即使用裝飾器。這種方式是使用sys模塊的settrace等方法重構了python的threading類:
#!/usr/bin/python
import threading
import sys
class KThread(threading.Thread):
"""Subclass of threading.Thread, with a kill() method."""
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.killed = False
def start(self):
"""Start the thread."""
self.__run_backup = self.run
"""Force the Thread to install our trace."""
self.run = self.__run
threading.Thread.start(self)
def __run(self):
"""Hacked run function, which installs the trace."""
sys.settrace(self.globaltrace)
self.__run_backup()
self.run = self.__run_backup
def globaltrace(self, frame, why, arg):
if why == 'call':
return self.localtrace
else:
return None
def localtrace(self, frame, why, arg):
if self.killed:
if why == 'line':
raise SystemExit()
return self.localtrace
def kill(self):
self.killed = True
然后,構造一個timeout裝飾器,這個裝飾器利用上面重載的KThread實現超時限制:
def timeout(seconds): def timeout_decorator(func): def _new_func(oldfunc, result, oldfunc_args, oldfunc_kwargs): result.append(oldfunc(*oldfunc_args, **oldfunc_kwargs)) def _(*args, **kwargs): result = [] '''create new args for _new_funcbecause we want to get the func return val to result list ''' new_kwargs = { 'oldfunc': func, 'result': result, 'oldfunc_args': args, 'oldfunc_kwargs': kwargs } thd = KThread(target=_new_func, args=(), kwargs=new_kwargs) thd.start() thd.join(seconds) alive = thd.isAlive() '''kill the child thread''' thd.kill() if alive: alert_exce = u'function timeout for [%d s].' % seconds raise Timeout(alert_exce) else: return result[0] _.__name__ = func.__name__ _.__doc__ = func.__doc__ return _ return timeout_decorator
這種方法使用起來十分簡單:只需要在需要超時控制的函數前面使用@timeout(sec)裝飾器即可。
但是這種方法有比較明顯的缺陷,因為其本質是使用將函數使用重載的線程來控制,一旦被添加裝飾器的函數內部使用了線程或者子進程等復雜的結構,而這些線程和子進程其實是無法獲得超時控制的,所以可能導致外層的超時控制無效。