超时功能,如果完成时间太长

Chr*_*fer 135 python

我有一个shell脚本循环遍历包含URL的文本文件:我想访问并截取屏幕截图.

这一切都做得很简单.该脚本初始化一个类,该类在运行时创建列表中每个站点的屏幕截图.有些站点需要非常长的时间来加载,有些站点可能根本没有加载.所以我想将screengrabber-function包装在一个超时脚本中,False如果它在10秒内无法完成,则使函数返回.

我满足于最简单的解决方案,也许设置一个异步计时器,无论在函数内部实际发生什么,它将在10秒后返回False?

Dav*_*yan 217

信号文档中描述了超时操作的过程.

基本思想是使用信号处理程序在一段时间间隔内设置警报,并在计时器到期后引发异常.

请注意,这仅适用于UNIX.

这是一个创建装饰器的实现(将以下代码保存为timeout.py).

from functools import wraps
import errno
import os
import signal

class TimeoutError(Exception):
    pass

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return wraps(func)(wrapper)

    return decorator
Run Code Online (Sandbox Code Playgroud)

这会创建一个名为的装饰器@timeout,可以应用于任何长时间运行的函数.

因此,在您的应用程序代码中,您可以像这样使用装饰器:

from timeout import timeout

# Timeout a long running function with the default expiry of 10 seconds.
@timeout
def long_running_function1():
    ...

# Timeout after 5 seconds
@timeout(5)
def long_running_function2():
    ...

# Timeout after 30 seconds, with the error "Connection timed out"
@timeout(30, os.strerror(errno.ETIMEDOUT))
def long_running_function3():
    ...
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这不是线程安全的:如果您使用多线程,则信号将被随机线程捕获.但对于单线程程序,这是最简单的解决方案. (68认同)
  • 仅供参考,在第一次"@timeout"之后有丢失的parens.它应该是`@timeout()def ...`. (7认同)
  • @wim我认为它只能在主线程中使用,因为如果你在工作线程中使用它,它会引发'ValueError:signal只能在主线程中工作'. (4认同)
  • 尼斯.另外,建议用`@ functools.wraps(func)`来装饰函数`wrapper` (3认同)
  • @Wim您能详细说明哪一部分不是线程安全的(我想这与`signal`有关)以及预期会有哪些副作用?另外,您是否碰巧拥有线程安全版本? (2认同)
  • 使用线程时,对此可行的替代方法是什么? (2认同)

Tho*_*hle 147

我用with声明重写了大卫的答案,它允许你这样做:

with timeout(seconds=3):
    time.sleep(4)
Run Code Online (Sandbox Code Playgroud)

这将引发TimeoutError.

代码仍在使用signal,因此仅限UNIX:

import signal

class timeout:
    def __init__(self, seconds=1, error_message='Timeout'):
        self.seconds = seconds
        self.error_message = error_message
    def handle_timeout(self, signum, frame):
        raise TimeoutError(self.error_message)
    def __enter__(self):
        signal.signal(signal.SIGALRM, self.handle_timeout)
        signal.alarm(self.seconds)
    def __exit__(self, type, value, traceback):
        signal.alarm(0)
Run Code Online (Sandbox Code Playgroud)

  • 有人可以推荐一个可行的解决方案,像这样,在线程中有效吗? (11认同)
  • Python <v3没有TimeoutError.但是可以很容易地编写一个自己的类,如下所述:http://stackoverflow.com/a/1319675/380038 (9认同)
  • 有趣的是,如果在`with Timeout(t)`上下文中引发了任何错误,仍然会调用`__exit__`,从而避免因引发`TimeOutError`而不是实际错误而引起的任何复杂化.这是一个非常可爱的解决方案. (8认同)
  • 您可以轻松地在装饰器中添加`@ timeout.timeout`作为静态方法.然后,您可以轻松地在装饰器或`with`语句之间进行选择. (4认同)
  • @Nick前段时间我创建了超时装饰器的版本,它适用于浮点数 - http://stackoverflow.com/questions/11901328/how-to-timeout-function-in-python-timeout-less-than-a-second/11901541#11901541 (3认同)
  • 如果函数正在捕获通用异常,则此方法不起作用,例如def bad_func():try:time.sleep(100),但异常:pass除外。 (2认同)
  • 唯一的问题是:`信号仅在主线程中有效` (2认同)
  • 我建议使用 [`signal.settimer`](https://docs.python.org/3/library/signal.html#signal.setitimer) 而不是 `signal.alarm` 来提高分辨率。通过此实现,最短超时为 1 秒。 (2认同)