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

sak*_*ken 9 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
Run Code Online (Sandbox Code Playgroud)

Aar*_*all 14

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

装饰器语法

为了更清楚,基于问题中的示例,用法如下:

@timeout(100)
def foo(arg1, kwarg1=None):
    '''time this out!'''
    something_worth_timing_out()
Run Code Online (Sandbox Code Playgroud)

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

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

foo = timeout(100)(foo)
Run Code Online (Sandbox Code Playgroud)

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

必要的进口

from functools import wraps
import errno
import os
import signal
Run Code Online (Sandbox Code Playgroud)

提高超时的例外情况

class TimeoutError(Exception):
    pass
Run Code Online (Sandbox Code Playgroud)

分析功能

这就是行中所谓的@timeout(timelimit).该timelimit参数将被锁定到内部功能,使这些功能的"倒闭潮",所谓的,因为他们近距离过来的数据:

def timeout(seconds=100, error_message=os.strerror(errno.ETIME)):
Run Code Online (Sandbox Code Playgroud)

这将返回一个函数,该函数将函数作为参数,下一行继续定义.此函数将返回包装原始函数的函数.:

    def decorator(func):
Run Code Online (Sandbox Code Playgroud)

这是一个超时装饰函数的函数:

        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)
Run Code Online (Sandbox Code Playgroud)

这是实际的包装器.在调用包装函数之前,它会设置一个信号,如果它没有及时完成并且异常,它将中断该函数:

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
Run Code Online (Sandbox Code Playgroud)

如果函数完成,这将返回结果:

            return result
Run Code Online (Sandbox Code Playgroud)

这将返回包装器.它确保包装函数从原始函数中获取属性,如docstrings,name,function signature ...

        return wraps(func)(wrapper)
Run Code Online (Sandbox Code Playgroud)

这是从原始调用返回装饰器的地方@timeout(timelimit):

    return decorator
Run Code Online (Sandbox Code Playgroud)

好处 wraps

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

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

foo(arg1, kwarg1=None)
    time this out!
Run Code Online (Sandbox Code Playgroud)

更好地利用 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
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,这非常有帮助. (2认同)