@timeout(timelimit)装饰器如何工作?

时间:2015-08-05 01:56:14

标签: python

我发现这个装饰器在Stack Overflow上超时了一个函数,我想知道是否有人可以详细解释它是如何工作的,因为代码非常优雅但根本不清楚。用法为@timeout(timelimit)

from functools import wraps
import errno
import os
import signal

class TimeoutError(Exception):
    pass

def timeout(seconds=100, 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

1 个答案:

答案 0 :(得分:14)

  

@timeout(timelimit)装饰器如何工作?

装饰器语法

更清楚的是,根据问题中的示例,用法如下:

@timeout(100)
def foo(arg1, kwarg1=None):
    '''time this out!'''
    something_worth_timing_out()

以上是装饰器语法。以下是语义上等同的:

def foo(arg1, kwarg1=None):
    '''time this out!'''
    something_worth_timing_out()

foo = timeout(100)(foo)

请注意,我们将包装原始foo的函数命名为“foo”。这就是装饰器语法的含义和作用。

必要的进口

from functools import wraps
import errno
import os
import signal

在超时时引发的异常

class TimeoutError(Exception):
    pass

功能分析

这就是行@timeout(timelimit)中所称的内容。 timelimit参数将被锁定到内部函数中,使这些函数成为“闭包”,因为它们会关闭数据而所谓的“闭包”:

def timeout(seconds=100, 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)

这是从原始调用@timeout(timelimit)

返回装饰器的地方
    return decorator

wraps

的好处

wrapps函数允许包装目标函数的函数获取该函数的文档,因为foo不再指向原始函数:

>>> help(foo)
Help on function foo in module __main__:

foo(arg1, kwarg1=None)
    time this out!

更好地使用wraps

为了进一步说明,wrapps返回一个装饰器,并且打算像这个函数一样使用。它会更好地写成:

def timeout(seconds=100, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)
        @wraps(func)
        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 wrapper
    return decorator