Python超时装饰器

Jav*_*aSa 5 python python-2.7 python-decorators

我正在使用这里提到的代码解决方案。
我是装饰人员的新手,如果我要编写以下内容,则不明白为什么该解决方案不起作用:

@timeout(10)
def main_func():
    nested_func()
    while True:
        continue

@timeout(5)
def nested_func():
   print "finished doing nothing"
Run Code Online (Sandbox Code Playgroud)

=>这样的结果将根本没有超时。我们将陷入无限循环。
但是,如果我@timeout从中删除注释,则会nested_func收到超时错误。
由于某种原因,我们不能同时在函数和嵌套函数上使用装饰器,任何想法为何以及如何能够使其正常工作,假设包含函数的超时必须始终大于嵌套的超时。

Blc*_*ght 6

这是signal您链接的装饰器使用的模块计时功能的限制。这是文档的相关部分(我添加了重点):

signal.alarm(time)

如果 time 非零,则此函数请求SIGALRMtime秒为单位向进程发送信号。任何先前安排的闹钟都会被取消(任何时候只能安排一个闹钟)。返回的值是任何先前设置的警报被传递之前的秒数。如果time为零,则不安排警报,并且取消任何安排的警报。如果返回值为零,则当前没有调度警报。(请参阅 Unix 手册页 alarm(2)。) 可用性:Unix。

所以,你看到的是,当你nested_func被调用时,它的定时器取消了外部函数的定时器。

您可以更新装饰器以注意alarm调用的返回值(这将是前一个警报(如果有)到期之前的时间)。获得正确的细节有点复杂,因为内部计时器需要跟踪其功能运行了多长时间,因此它可以修改前一个计时器的剩余时间。这是一个未经测试的装饰器版本,我认为它大部分是正确的(但我不完全确定它在所有异常情况下都能正常工作):

import time
import signal

class TimeoutError(Exception):
    def __init__(self, value = "Timed Out"):
        self.value = value
    def __str__(self):
        return repr(self.value)

def timeout(seconds_before_timeout):
    def decorate(f):
        def handler(signum, frame):
            raise TimeoutError()
        def new_f(*args, **kwargs):
            old = signal.signal(signal.SIGALRM, handler)
            old_time_left = signal.alarm(seconds_before_timeout)
            if 0 < old_time_left < second_before_timeout: # never lengthen existing timer
                signal.alarm(old_time_left)
            start_time = time.time()
            try:
                result = f(*args, **kwargs)
            finally:
                if old_time_left > 0: # deduct f's run time from the saved timer
                    old_time_left -= time.time() - start_time
                signal.signal(signal.SIGALRM, old)
                signal.alarm(old_time_left)
            return result
        new_f.func_name = f.func_name
        return new_f
    return decorate
Run Code Online (Sandbox Code Playgroud)