[英]Why does this function not work when used as a decorator?
更新 :正如Fooz先生所指出的,包装器的功能版本有一个bug,所以我恢复了原来的类实现。 我把代码放在GitHub上:
https://github.com/nofatclips/timeout/commits/master
有两个提交,一个工作(使用“导入”解决方法),第二个提交。
问题的根源似乎是pickle#dumps
函数,它只在函数调用时吐出一个标识符。 当我调用Process
,该标识符指向函数的修饰版本,而不是原始函数。
原始信息 :
我试图写一个函数装饰器来包装一个进程中的长任务,如果超时到期将会被终止。 我想出了这个(工作但不优雅)的版本:
from multiprocessing import Process
from threading import Timer
from functools import partial
from sys import stdout
def safeExecution(function, timeout):
thread = None
def _break():
#stdout.flush()
#print (thread)
thread.terminate()
def start(*kw):
timer = Timer(timeout, _break)
timer.start()
thread = Process(target=function, args=kw)
ret = thread.start() # TODO: capture return value
thread.join()
timer.cancel()
return ret
return start
def settimeout(timeout):
return partial(safeExecution, timeout=timeout)
#@settimeout(1)
def calculatePrimes(maxPrimes):
primes = []
for i in range(2, maxPrimes):
prime = True
for prime in primes:
if (i % prime == 0):
prime = False
break
if (prime):
primes.append(i)
print ("Found prime: %s" % i)
if __name__ == '__main__':
print (calculatePrimes)
a = settimeout(1)
calculatePrime = a(calculatePrimes)
calculatePrime(24000)
如您所见,我注释掉装饰器并将calculatePrimes的修改版本分配给calculatePrime。 如果我试图将它重新分配给同一个变量,我会在尝试调用装饰版本时遇到“无法解决:属性查找builtins.function失败”错误。
任何人都知道引擎盖下发生了什么? 当我将装饰版本分配给引用它的标识符时,原始函数是否变成了不同的东西?
更新 :要重现错误,我只需将主要部分更改为
if __name__ == '__main__':
print (calculatePrimes)
a = settimeout(1)
calculatePrimes = a(calculatePrimes)
calculatePrimes(24000)
#sleep(2)
产量:
Traceback (most recent call last): File "c:\\Users\\mm\\Desktop\\ING.SW\\python\\thread2.py", line 49, in <module> calculatePrimes(24000) File "c:\\Users\\mm\\Desktop\\ING.SW\\python\\thread2.py", line 19, in start ret = thread.start() File "C:\\Python33\\lib\\multiprocessing\\process.py", line 111, in start self._popen = Popen(self) File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 241, in __init__ dump(process_obj, to_child, HIGHEST_PROTOCOL) File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 160, in dump ForkingPickler(file, protocol).dump(obj) _pickle.PicklingError: Can't pickle <class 'function'>: attribute lookup builtin s.function failed Traceback (most recent call last): File "<string>", line 1, in <module> File "C:\\Python33\\lib\\multiprocessing\\forking.py", line 344, in main self = load(from_parent) EOFError
PS我还写了一个safeExecution的类版本,它具有完全相同的行为。
将函数移动到脚本导入的模块。
如果函数在模块的顶层定义,则只能在python中进行选择。 脚本中定义的Ones默认情况下不可选。 基于模块的函数被腌制为两个字符串:模块的名称和函数的名称。 它们通过动态导入模块然后按名称查找函数对象(因此仅限顶级函数的限制)来进行unpickled。
可以扩展pickle处理程序以支持半泛型函数和lambda酸洗,但这样做可能会很棘手。 特别是,如果要正确处理装饰器和嵌套函数之类的东西,可能很难重构完整的命名空间树。 如果你想这样做,最好使用Python 2.7或更高版本或Python 3.3或更高版本(早期版本在cPickle
和pickle
的调度程序中有一个错误,这是一个令人不愉快的解决方法)。
是否有一种简单的方法来挑选python函数(或以其他方式序列化其代码)?
http://bugs.python.org/issue7689
编辑 :
至少在Python 2.6中,如果脚本只包含if __name__
块,则脚本可以if __name__
,脚本从模块导入calculatePrimes
和settimeout
,如果内部start
函数的名称是猴子修补的:
def safeExecution(function, timeout):
...
def start(*kw):
...
start.__name__ = function.__name__ # ADD THIS LINE
return start
还有第二个问题与Python的变量范围规则有关。 在start
内部对thread
变量的赋值创建了一个影子变量,其范围仅限于对start
函数的一个评估。 它不会分配给封闭范围中找到的thread
变量。 您不能使用global
关键字来覆盖范围,因为您需要和中间范围和Python仅完全支持操作本地最多和全局范围,而不是任何中间范围。 您可以通过将线程对象放在容纳在中间范围内的容器中来克服此问题。 这是如何做:
def safeExecution(function, timeout):
thread_holder = [] # MAKE IT A CONTAINER
def _break():
#stdout.flush()
#print (thread)
thread_holder[0].terminate() # REACH INTO THE CONTAINER
def start(*kw):
...
thread = Process(target=function, args=kw)
thread_holder.append(thread) # MUTATE THE CONTAINER
...
start.__name__ = function.__name__ # MAKES THE PICKLING WORK
return start
不确定为什么你会遇到这个问题,但要回答你的标题问题:为什么装饰师不起作用?
将参数传递给装饰器时,需要将代码结构略有不同。 基本上你必须将装饰器实现为一个带有__init__
和__call__
。
在init中,您收集发送给装饰器的参数,并在调用中,您将获得您装饰的函数:
class settimeout(object):
def __init__(self, timeout):
self.timeout = timeout
def __call__(self, func):
def wrapped_func(n):
func(n, self.timeout)
return wrapped_func
@settimeout(1)
def func(n, timeout):
print "Func is called with", n, 'and', timeout
func(24000)
这应该让你至少去装饰前面。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.