Python 中的函数属性
Function attributes in Python
我问这个问题是
的延续
我找到了一种无需线程等的方法。只需不时进行简单检查即可。
这是我的装饰器:
def time_limit(seconds):
def decorator(func):
func.info = threading.local()
def check_timeout():
if hasattr(func.info, 'end_time'):
if time.time() > func.info.end_time:
raise TimeoutException
func.check_timeout = check_timeout
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not hasattr(func.info, 'end_time'):
func.info.end_time = time.time() + seconds
return func(*args, **kwargs)
return wrapper
return decorator
和用法:
@time_limit(60)
def algo():
do_something()
algo.check_timeout()
do_something_else()
它在本地主机上运行良好,但在使用 mod_wsgi 和 django 的服务器 apache 上失败。
- 第一个问题。注意
hasattr
?我应该添加它,因为有时我会收到错误 '_thread.local'
has no attribute end_time
- 为什么我需要 threading.local?正如@Graham Dumpleton 指出的那样,我们不能有一个单一的全局结束时间,因为后续请求会进来并覆盖它。因此,如果第一个请求没有完成,它的
end_time
将重置为为后面的请求设置的任何内容。
问题是这种方法没有帮助。假设我有以下 运行s. 的会话
首先 运行 - 在超时发生之前 - 运行s 完美
第二个 运行 - 在超时发生之前 - 运行s 完美
第三个 运行 - 发生超时 - 引发 TimeoutException
所有后续调用都会引发 TimeoutException
,无论是否发生。
似乎所有后续调用都会查看第三个 运行 的 end_time 副本,并且由于存在超时,它们也会引发 Timeout
.
如何为每个函数调用本地化 end_time?谢谢。
编辑:
感谢@miki725 和@Antti Haapala 我简化了我的函数并使它变得简单 class:
class TimeLimit(object):
def __init__(self, timeout=60):
self.timeout = timeout
self.end = None
def check_timeout(self):
if self.end and time.time() > self.end:
raise TimeoutException
else:
self.start()
def start(self):
if not self.end:
self.end = time.time() + self.timeout
不过我把timer传给函数很不方便,因为algo其实是很复杂的递归函数。
所以,我做了以下操作:
timer = TimeLimit() # fails. I think because it is global
def algo()
do_stuff()
timer.check_timeout()
do_another_stuff()
sub_algo() # check inside it to
algo()
...
有什么方法可以使 timer
线程安全。 pseudoprivate _timer
有什么帮助吗?
问题是 hasattr
保护 end_time
的 设置 :
if not hasattr(func.info, 'end_time'):
func.info.end_time = time.time() + seconds
end_time
只为 每个线程 设置一次。线程是长寿命的并且服务于许多请求;现在绝对时间限制为每个线程设置一次,但永远不会被清除。
至于这个装饰器的用处,我觉得不高明;我会说这是彻头彻尾的丑陋。为什么要滥用装饰器,与线程局部变量等进行单闭包可以做的事情:
def timelimit_checker(time_limit):
end = time.time() + time_limit
def checker():
if time.time() > end:
raise TimeoutException
return checker
def sub_algo(check_limit):
...
check_limit()
...
def algo():
check_limit = timelimit_checker(60)
...
check_limit()
...
subalgo(check_limit)
问题是您在函数对象本身上添加了 end_time
。由于每个线程都将导入所有 Python 模块,因此实际上您只会设置 end_time
n
线程数的 运行 倍(这似乎是您的情况2).
要解决这个问题,您可以始终在每个线程中设置 end_time
,但是这对我来说似乎并不优雅,因为您对将要执行的内容做出了一些假设。
其他解决方案是使用 classes。这将允许在 class 实例中保持状态,因此不会发生此问题。
class ExecuteWithTimeout(object):
def __init__(self, to_execute, timeout):
self.to_execute = to_execute
self.timeout = timeout
self.end = None
def check_timeout(self):
if time.time() > self.end:
raise TimeoutException
def __call__(self, *args, **kwargs):
self.end = time.time() + self.timeout
result = self.to_execute(*args, **kwargs)
self.check_timeout()
return result
def usage():
stuff = ExecuteWithTimeout(do_something, 10)()
do_something_else(stuff)
另一种方法是使用上下文管理器:
@contextmanager
def timeout_limit(timeout):
end = time.time() + self.timeout
yield
if time.time() > end:
raise TimeoutException
def usage():
with timeout_limit(10):
do_stuff()
more_things()
或者更好的是,您可以将两者结合起来!
class TimeLimit(object):
def __init__(self, timeout=60):
self.timeout = timeout
self.end = None
def __enter__(self):
self.end = time.time() + self.timeout
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.check_timeout()
def check_timeout(self):
if self.end and time.time() > self.end:
raise TimeoutException
def algo():
with TimeLimit(2) as timer:
time.sleep(1)
timer.check_timeout()
time.sleep(1)
timer.check_timeout()
更新您的更新:
timer = TimeLimit() # fails. I think because it is global
def algo():
...
使用上面的 class 对您没有帮助,因为 class 将是一个线程级实例,它会让您回到最初的问题。问题在于保持线程级状态,因此将它存储在 class 中还是作为函数对象属性并不重要。如果这些函数需要,您的函数应该明确地将状态传递给内部函数。你不应该依赖于使用全局状态来这样做:
def algo():
with TimeLimit(2) as timer:
do_stuff(timer)
timer.check_timeout()
do_more_stuff(timer)
timer.check_timeout()
我问这个问题是
我找到了一种无需线程等的方法。只需不时进行简单检查即可。
这是我的装饰器:
def time_limit(seconds):
def decorator(func):
func.info = threading.local()
def check_timeout():
if hasattr(func.info, 'end_time'):
if time.time() > func.info.end_time:
raise TimeoutException
func.check_timeout = check_timeout
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not hasattr(func.info, 'end_time'):
func.info.end_time = time.time() + seconds
return func(*args, **kwargs)
return wrapper
return decorator
和用法:
@time_limit(60)
def algo():
do_something()
algo.check_timeout()
do_something_else()
它在本地主机上运行良好,但在使用 mod_wsgi 和 django 的服务器 apache 上失败。
- 第一个问题。注意
hasattr
?我应该添加它,因为有时我会收到错误'_thread.local'
has no attributeend_time
- 为什么我需要 threading.local?正如@Graham Dumpleton 指出的那样,我们不能有一个单一的全局结束时间,因为后续请求会进来并覆盖它。因此,如果第一个请求没有完成,它的
end_time
将重置为为后面的请求设置的任何内容。 问题是这种方法没有帮助。假设我有以下 运行s. 的会话
首先 运行 - 在超时发生之前 - 运行s 完美
第二个 运行 - 在超时发生之前 - 运行s 完美
第三个 运行 - 发生超时 - 引发 TimeoutException
所有后续调用都会引发 TimeoutException
,无论是否发生。
似乎所有后续调用都会查看第三个 运行 的 end_time 副本,并且由于存在超时,它们也会引发 Timeout
.
如何为每个函数调用本地化 end_time?谢谢。
编辑: 感谢@miki725 和@Antti Haapala 我简化了我的函数并使它变得简单 class:
class TimeLimit(object):
def __init__(self, timeout=60):
self.timeout = timeout
self.end = None
def check_timeout(self):
if self.end and time.time() > self.end:
raise TimeoutException
else:
self.start()
def start(self):
if not self.end:
self.end = time.time() + self.timeout
不过我把timer传给函数很不方便,因为algo其实是很复杂的递归函数。 所以,我做了以下操作:
timer = TimeLimit() # fails. I think because it is global
def algo()
do_stuff()
timer.check_timeout()
do_another_stuff()
sub_algo() # check inside it to
algo()
...
有什么方法可以使 timer
线程安全。 pseudoprivate _timer
有什么帮助吗?
问题是 hasattr
保护 end_time
的 设置 :
if not hasattr(func.info, 'end_time'):
func.info.end_time = time.time() + seconds
end_time
只为 每个线程 设置一次。线程是长寿命的并且服务于许多请求;现在绝对时间限制为每个线程设置一次,但永远不会被清除。
至于这个装饰器的用处,我觉得不高明;我会说这是彻头彻尾的丑陋。为什么要滥用装饰器,与线程局部变量等进行单闭包可以做的事情:
def timelimit_checker(time_limit):
end = time.time() + time_limit
def checker():
if time.time() > end:
raise TimeoutException
return checker
def sub_algo(check_limit):
...
check_limit()
...
def algo():
check_limit = timelimit_checker(60)
...
check_limit()
...
subalgo(check_limit)
问题是您在函数对象本身上添加了 end_time
。由于每个线程都将导入所有 Python 模块,因此实际上您只会设置 end_time
n
线程数的 运行 倍(这似乎是您的情况2).
要解决这个问题,您可以始终在每个线程中设置 end_time
,但是这对我来说似乎并不优雅,因为您对将要执行的内容做出了一些假设。
其他解决方案是使用 classes。这将允许在 class 实例中保持状态,因此不会发生此问题。
class ExecuteWithTimeout(object):
def __init__(self, to_execute, timeout):
self.to_execute = to_execute
self.timeout = timeout
self.end = None
def check_timeout(self):
if time.time() > self.end:
raise TimeoutException
def __call__(self, *args, **kwargs):
self.end = time.time() + self.timeout
result = self.to_execute(*args, **kwargs)
self.check_timeout()
return result
def usage():
stuff = ExecuteWithTimeout(do_something, 10)()
do_something_else(stuff)
另一种方法是使用上下文管理器:
@contextmanager
def timeout_limit(timeout):
end = time.time() + self.timeout
yield
if time.time() > end:
raise TimeoutException
def usage():
with timeout_limit(10):
do_stuff()
more_things()
或者更好的是,您可以将两者结合起来!
class TimeLimit(object):
def __init__(self, timeout=60):
self.timeout = timeout
self.end = None
def __enter__(self):
self.end = time.time() + self.timeout
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.check_timeout()
def check_timeout(self):
if self.end and time.time() > self.end:
raise TimeoutException
def algo():
with TimeLimit(2) as timer:
time.sleep(1)
timer.check_timeout()
time.sleep(1)
timer.check_timeout()
更新您的更新:
timer = TimeLimit() # fails. I think because it is global
def algo():
...
使用上面的 class 对您没有帮助,因为 class 将是一个线程级实例,它会让您回到最初的问题。问题在于保持线程级状态,因此将它存储在 class 中还是作为函数对象属性并不重要。如果这些函数需要,您的函数应该明确地将状态传递给内部函数。你不应该依赖于使用全局状态来这样做:
def algo():
with TimeLimit(2) as timer:
do_stuff(timer)
timer.check_timeout()
do_more_stuff(timer)
timer.check_timeout()