简体   繁体   English

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

[英]Why does this function not work when used as a decorator?

UPDATE : As noted by Mr. Fooz, the functional version of the wrapper has a bug, so I reverted to the original class implementation. 更新 :正如Fooz先生所指出的,包装器的功能版本有一个bug,所以我恢复了原来的类实现。 I've put the code up on GitHub: 我把代码放在GitHub上:

https://github.com/nofatclips/timeout/commits/master https://github.com/nofatclips/timeout/commits/master

There are two commits, one working (using the "import" workaround) the second one broken. 有两个提交,一个工作(使用“导入”解决方法),第二个提交。

The source of the problem seems to be the pickle#dumps function, which just spits out an identifier when called on an function. 问题的根源似乎是pickle#dumps函数,它只在函数调用时吐出一个标识符。 By the time I call Process , that identifier points to the decorated version of the function, rather than the original one. 当我调用Process ,该标识符指向函数的修饰版本,而不是原始函数。


ORIGINAL MESSAGE : 原始信息

I was trying to write a function decorator to wrap a long task in a Process that would be killed if a timeout expires. 我试图写一个函数装饰器来包装一个进程中的长任务,如果超时到期将会被终止。 I came up with this (working but not elegant) version: 我想出了这个(工作但不优雅)的版本:

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)

As you can see, I commented out the decorator and assigned the modified version of calculatePrimes to calculatePrime. 如您所见,我注释掉装饰器并将calculatePrimes的修改版本分配给calculatePrime。 If I tried to reassign it to the same variable, I'd get a "Can't pickle : attribute lookup builtins.function failed" error when trying to call the decorated version. 如果我试图将它重新分配给同一个变量,我会在尝试调用装饰版本时遇到“无法解决:属性查找builtins.function失败”错误。

Anybody has any idea of what is happening under the hood? 任何人都知道引擎盖下发生了什么? Is the original function being turned into something different when I assign the decorated version to the identifier referencing it? 当我将装饰版本分配给引用它的标识符时,原始函数是否变成了不同的东西?

UPDATE : To reproduce the error, I just change the main part to 更新 :要重现错误,我只需将主要部分更改为

if __name__ == '__main__':
    print (calculatePrimes)
    a = settimeout(1)
    calculatePrimes = a(calculatePrimes)
    calculatePrimes(24000)
    #sleep(2)

which yields: 产量:

 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 I also wrote a class version of safeExecution, which has exactly the same behaviour. PS我还写了一个safeExecution的类版本,它具有完全相同的行为。

Move the function to a module that's imported by your script. 将函数移动到脚本导入的模块。

Functions are only picklable in python if they're defined at the top level of a module. 如果函数在模块的顶层定义,则只能在python中进行选择。 Ones defined in scripts are not picklable by default. 脚本中定义的Ones默认情况下不可选。 Module-based functions are pickled as two strings: the name of the module, and the name of the function. 基于模块的函数被腌制为两个字符串:模块的名称和函数的名称。 They're unpickled by dynamically importing the module then looking up the function object by name (hence the restriction on top-level-only functions). 它们通过动态导入模块然后按名称查找函数对象(因此仅限顶级函数的限制)来进行unpickled。

It's possible to extend the pickle handlers to support semi-generic function and lambda pickling, but doing so can be tricky. 可以扩展pickle处理程序以支持半泛型函数和lambda酸洗,但这样做可能会很棘手。 In particular, it can be difficult to reconstruct the full namespace tree if you want to properly handle things like decorators and nested functions. 特别是,如果要正确处理装饰器和嵌套函数之类的东西,可能很难重构完整的命名空间树。 If you want to do this, it's best to use Python 2.7 or later or Python 3.3 or later (earlier versions have a bug in the dispatcher of cPickle and pickle that's unpleasant to work around). 如果你想这样做,最好使用Python 2.7或更高版本或Python 3.3或更高版本(早期版本在cPicklepickle的调度程序中有一个错误,这是一个令人不愉快的解决方法)。

Is there an easy way to pickle a python function (or otherwise serialize its code)? 是否有一种简单的方法来挑选python函数(或以其他方式序列化其代码)?

Python: pickling nested functions Python:pickling嵌套函数

http://bugs.python.org/issue7689 http://bugs.python.org/issue7689

EDIT : 编辑

At least in Python 2.6, the pickling works fine for me if the script only contains the if __name__ block, the script imports calculatePrimes and settimeout from a module, and if the inner start function's name is monkey-patched: 至少在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

There's a second problem that's related to Python's variable scoping rules. 还有第二个问题与Python的变量范围规则有关。 The assignment to the thread variable inside start creates a shadow variable whose scope is limited to one evaluation of the start function. start内部对thread变量的赋值创建了一个影子变量,其范围仅限于对start函数的一个评估。 It does not assign to the thread variable found in the enclosing scope. 不会分配给封闭范围中找到的thread变量。 You can't use the global keyword to override the scope because you want and intermediate scope and Python only has full support for manipulating the local-most and global-most scopes, not any intermediate ones. 您不能使用global关键字来覆盖范围,因为您需要和中间范围和Python仅完全支持操作本地最多和全局范围,而不是任何中间范围。 You can overcome this problem by placing the thread object in a container that's housed in the intermediate scope. 您可以通过将线程对象放在容纳在中间范围内的容器中来克服此问题。 Here's how: 这是如何做:

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

Not sure really why you get that problem, but to answer your title question: Why does the decorator not work? 不确定为什么你会遇到这个问题,但要回答你的标题问题:为什么装饰师不起作用?

When you pass arguments to a decorator, you need to structure the code slightly different. 将参数传递给装饰器时,需要将代码结构略有不同。 Essentially you have to implement the decorator as a class with an __init__ and an __call__ . 基本上你必须将装饰器实现为一个带有__init____call__

In the init, you collect the arguments that you send to the decorator, and in the call, you'll get the function you decorate: 在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)

This should get you going on the decorator front at least. 这应该让你至少去装饰前面。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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