Python超时d

2024-04-25 07:44:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用前面提到的代码解决方案here
我是装饰设计师的新手,如果我想写如下内容,我不明白为什么这个解决方案不起作用:

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

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

=>;此操作的结果根本不会超时。我们将陷入无休止的循环。
但是,如果从nested_func中删除@timeout注释,则会出现超时错误。
由于某些原因,我们不能同时在函数和嵌套函数上使用decorator,不管我为什么以及如何更正它,假设包含函数的超时值总是大于嵌套超时值。


Tags: 函数代码内容heremaindeftimeout装饰
3条回答

Python的PyPI库中有一个更好的timeout decorator版本。它同时支持基于UNIX和非基于UNIX的操作系统。提到信号的部分—特别是针对UNIX的部分。

假设您没有使用UNIX。下面是装饰程序中的代码片段,它显示了一个参数列表,您可以根据需要使用这些参数。

def timeout(seconds=None, use_signals=True, timeout_exception=TimeoutError, exception_message=None)

用于在非UNIX基本操作系统上实现。这就是我要做的:

import time
import timeout_decorator

@timeout_decorator.timeout(10, use_signals=False)
def main_func():
    nested_func()
    while True:
        continue

@timeout_decorator.timeout(5, use_signals=False)
def nested_func():
    print "finished doing nothing"

如果你注意到了,我正在做使用'u signals=False。仅此而已,你该走了。

这是signal模块的计时函数的一个限制,您链接的decorator使用这些函数。这是相关的piece of the documentation(我加上了重点):

signal.alarm(time)

If time is non-zero, this function requests that a SIGALRM signal be sent to the process in time seconds. Any previously scheduled alarm is canceled (only one alarm can be scheduled at any time). The returned value is then the number of seconds before any previously set alarm was to have been delivered. If time is zero, no alarm is scheduled, and any scheduled alarm is canceled. If the return value is zero, no alarm is currently scheduled. (See the Unix man page alarm(2).) Availability: Unix.

所以,您看到的是,当调用nested_func时,它的计时器会取消外部函数的计时器。

您可以更新decorator以注意alarm调用的返回值(这将是上一个警报(如果有的话)到期之前的时间)。正确地获取细节有点复杂,因为内部计时器需要跟踪其函数运行的时间,因此它可以修改前一个计时器上剩余的时间。这里有一个未经测试的decorator版本,我认为它基本上是正确的(但我不完全确定它是否适用于所有异常情况):

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所指出的,不能为嵌套的装饰器使用信号,但是可以使用多处理器来存档。

您可以使用这个decorator,它支持嵌套的decorators:https://github.com/bitranox/wrapt_timeout_decorator

正如abager1999在他的博客中指出的那样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 catched
# 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 catched 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')

相关问题 更多 >