0

更新:正如 Fooz 先生所指出的,包装器的功能版本有一个错误,所以我恢复到原来的类实现。我已经把代码放在了 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我还写了一个安全执行的类版本,它具有完全相同的行为。

4

2 回答 2

3

将函数移动到脚本导入的模块。

如果函数是在模块的顶层定义的,则函数只能在 python 中进行选择。默认情况下,在脚本中定义的那些是不可挑选的。基于模块的函数被提取为两个字符串:模块的名称和函数的名称。它们是通过动态导入模块然后按名称查找函数对象来解开的(因此限制了仅顶级函数)。

可以扩展 pickle 处理程序以支持半泛型函数和 lambda 酸洗,但这样做可能会很棘手。特别是,如果您想正确处理诸如装饰器和嵌套函数之类的事情,则可能很难重建完整的命名空间树。如果你想这样做,最好使用 Python 2.7 或更高版本或 Python 3.3 或更高版本(早期版本的调度程序中有一个错误,cPickle并且pickle很难解决)。

有没有一种简单的方法来腌制一个 python 函数(或者序列化它的代码)?

Python:酸洗嵌套函数

http://bugs.python.org/issue7689

编辑

至少在 Python 2.6 中,如果脚本只包含if __name__块、脚本导入calculatePrimessettimeout从模块中导入,并且内部start函数的名称是猴子补丁,那么酸洗对我来说很好:

def safeExecution(function, timeout):
    ...    
    def start(*kw):
        ...

    start.__name__ = function.__name__ # ADD THIS LINE

    return start

第二个问题与 Python 的变量作用域规则有关。thread对内部变量的赋值start会创建一个影子变量,其范围仅限于对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
于 2013-02-20T22:27:23.800 回答
0

不确定你为什么会遇到这个问题,但要回答你的标题问题:为什么装饰器不起作用?

当您将参数传递给装饰器时,您需要构建稍微不同的代码。本质上,您必须将装饰器实现为带有 an__init__和 an的类__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)

这至少应该让你在装饰器的前面。

于 2013-02-20T22:28:41.483 回答