我发现这个装饰器在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
答案 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