繁体   English   中英

为什么这个功能在用作装饰器时不起作用?

[英]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或更高版本(早期版本在cPicklepickle的调度程序中有一个错误,这是一个令人不愉快的解决方法)。

是否有一种简单的方法来挑选python函数(或以其他方式序列化其代码)?

Python:pickling嵌套函数

http://bugs.python.org/issue7689

编辑

至少在Python 2.6中,如果脚本只包含if __name__块,则脚本可以if __name__ ,脚本从模块导入calculatePrimessettimeout ,如果内部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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM