Python超时装饰器

新手上路,请多包涵

我正在使用 这里 提到的代码解决方案。

我是装饰器的新手,不明白为什么如果我想写如下内容,这个解决方案不起作用:

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

@timeout(5)
def nested_func():
   print "finished doing nothing"

=> 结果根本不会超时。我们将陷入无限循环。

但是,如果我从 nested_func 中删除 @timeout 注释,我会收到超时错误。

出于某种原因,我们不能同时在函数和嵌套函数上使用装饰器,任何想法为什么以及如何纠正它才能工作,假设包含函数超时总是必须大于嵌套超时。

原文由 JavaSa 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 762
2 个回答

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

signal.alarm(time)

如果时间不为零,此函数请求 SIGALRM 信号在 time 秒后发送到进程。 取消任何先前安排的闹钟(任何时候只能安排一个闹钟)。 返回值是之前设置的任何警报被发送之前的秒数。如果 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

原文由 Blckknght 发布,翻译遵循 CC BY-SA 4.0 许可协议

正如 Blckknght 指出的那样,您不能将信号用于嵌套装饰器 - 但您可以使用多处理来实现这一点。

您可能会使用这个装饰器,它支持嵌套装饰器: https ://github.com/bitranox/wrapt_timeout_decorator

正如 ABADGER1999 在他的博客 https://anonbadger.wordpress.com/2018/12/15/python-signal-handlers-and-exceptions/ 中指出的那样,使用信号和 TimeoutException 可能不是最好的主意 - 因为它可以陷入装饰功能。

当然,您可以使用自己的异常,派生自基本异常类,但代码可能仍无法按预期工作 - 请参阅下一个示例 - 您可以在 jupyter 中尝试: https ://mybinder.org/v2/gh /bitranox/wrapt_timeout_decorator/master?filepath=jupyter_test_wrapt_timeout_decorator.ipynb

 import time
from wrapt_timeout_decorator import *

# caveats when using signals - the TimeoutError raised by the signal may be caught
# inside the decorated function.
# So You might use Your own Exception, derived from the base Exception Class.
# In Python-3.7.1 stdlib there are over 300 pieces of code that will catch your timeout
# if you were to base an exception on Exception. If you base your exception on BaseException,
# there are still 231 places that can potentially catch your exception.
# You should use use_signals=False if You want to make sure that the timeout is handled correctly !
# therefore the default value for use_signals = False on this decorator !

@timeout(5, use_signals=True)
def mytest(message):
    try:
        print(message)
        for i in range(1,10):
            time.sleep(1)
            print('{} seconds have passed - lets assume we read a big file here'.format(i))
    # TimeoutError is a Subclass of OSError - therefore it is caught here !
    except OSError:
        for i in range(1,10):
            time.sleep(1)
            print('Whats going on here ? - Ooops the Timeout Exception is catched by the OSError ! {}'.format(i))
    except Exception:
        # even worse !
        pass
    except:
        # the worst - and exists more then 300x in actual Python 3.7 stdlib Code !
        # so You never really can rely that You catch the TimeoutError when using Signals !
        pass

if __name__ == '__main__':
    try:
        mytest('starting')
        print('no Timeout Occured')
    except TimeoutError():
        # this will never be printed because the decorated function catches implicitly the TimeoutError !
        print('Timeout Occured')

原文由 bitranox 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题