具有多处理功能的Python装饰器失败

时间:2012-02-17 22:59:43

标签: python decorator multiprocessing

我想在函数上使用装饰器,我将随后传递给多处理池。但是,代码失败了" PicklingError:不能pickle:属性查找__builtin__。函数失败"。我不太明白为什么它在这里失败了。我确信它很简单,但我无法找到它。以下是最小的"工作"例。我认为使用functools函数就足以让它工作了。

如果我注释掉功能装饰,它的工作没有问题。我在这里误解了multiprocessing是什么意思?有没有办法让这项工作?

编辑:在添加可调用类装饰器函数装饰器之后,事实证明函数装饰器按预期工作。可调用类装饰器继续失败。什么是可调用的类版本,以防止它被腌制?

import random
import multiprocessing
import functools

class my_decorator_class(object):
    def __init__(self, target):
        self.target = target
        try:
            functools.update_wrapper(self, target)
        except:
            pass

    def __call__(self, elements):
        f = []
        for element in elements:
            f.append(self.target([element])[0])
        return f

def my_decorator_function(target):
    @functools.wraps(target)
    def inner(elements):
        f = []
        for element in elements:
            f.append(target([element])[0])
        return f
    return inner

@my_decorator_function
def my_func(elements):
    f = []
    for element in elements:
        f.append(sum(element))
    return f

if __name__ == '__main__':
    elements = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)]
    pool = multiprocessing.Pool(processes=4)
    results = [pool.apply_async(my_func, ([e],)) for e in elements]
    pool.close()
    f = [r.get()[0] for r in results]
    print(f)

3 个答案:

答案 0 :(得分:11)

问题是,泡菜需要有一些方法来重新组装你挑选的所有东西。请点击此处查看可以腌制的清单:

http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled

当腌制my_func时,需要腌制以下组件:

  • my_decorator_class的一个实例,名为my_func

    这很好。 Pickle将存储类的名称并挑选其__dict__内容。取消排版时,它使用名称查找类,然后创建实例并填写__dict__内容。但是,__dict__内容存在问题......

  • 存储在my_func.target中的原始my_func实例

    这不太好。它是顶层的功能,通常可以腌制。 Pickle将存储该函数的名称。然而,问题是名称“my_func”不再绑定到未修饰的函数,它绑定到修饰函数。这意味着pickle将无法查找未修饰的函数来重新创建对象。可悲的是,pickle没有办法知道它试图腌制的对象总是可以在名称 main .my_func下找到。

您可以像这样更改它,它将起作用:

import random
import multiprocessing
import functools

class my_decorator(object):
    def __init__(self, target):
        self.target = target
        try:
            functools.update_wrapper(self, target)
        except:
            pass

    def __call__(self, candidates, args):
        f = []
        for candidate in candidates:
            f.append(self.target([candidate], args)[0])
        return f

def old_my_func(candidates, args):
    f = []
    for c in candidates:
        f.append(sum(c))
    return f

my_func = my_decorator(old_my_func)

if __name__ == '__main__':
    candidates = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)]
    pool = multiprocessing.Pool(processes=4)
    results = [pool.apply_async(my_func, ([c], {})) for c in candidates]
    pool.close()
    f = [r.get()[0] for r in results]
    print(f)

您已经观察到,当类没有时,装饰器功能会起作用。我相信这是因为functools.wraps修改了修饰函数,以便它具有它包装的函数的名称和其他属性。就pickle模块而言,它与普通的顶级函数无法区分,因此它通过存储其名称来腌制它。在unpickling时,名称绑定到装饰函数,所以一切都工作。

答案 1 :(得分:1)

如果您想要装饰器太糟糕(例如我),也可以在函数字符串上使用exec()命令来规避提到的酸洗。

我希望能够将所有参数传递给原始函数,然后依次使用它们。以下是我的代码。

首先,我制作了一个make_functext()函数以将目标函数对象转换为字符串。为此,我使用了getsource()模块中的inspect函数(请参见文档here,并注意它无法从编译后的代码中检索源代码等)。在这里:

from inspect import getsource

def make_functext(func):
    ft = '\n'.join(getsource(func).split('\n')[1:]) # Removing the decorator, of course
    ft = ft.replace(func.__name__, 'func')          # Making function callable with 'func'
    ft = ft.replace('#§ ', '').replace('#§', '')    # For using commented code starting with '#§'
    ft = ft.strip()                                 # In case the function code was indented
    return ft

在以下_worker()函数中使用,它将作为进程的目标:

def _worker(functext, args):
    scope = {}               # This is needed to keep executed definitions
    exec(functext, scope)
    scope['func'](args)      # Using func from scope

最后,这是我的装饰器:

from multiprocessing import Process 

def parallel(num_processes, **kwargs):
    def parallel_decorator(func, num_processes=num_processes):
        functext = make_functext(func)
        print('This is the parallelized function:\n', functext)
        def function_wrapper(funcargs, num_processes=num_processes):
            workers = []
            print('Launching processes...')
            for k in range(num_processes):
                p = Process(target=_worker, args=(functext, funcargs[k])) # use args here
                p.start()
                workers.append(p)
        return function_wrapper
    return parallel_decorator

最终可以通过定义如下函数来使用代码:

@parallel(4)
def hello(args):
    #§ from time import sleep     # use '#§' to avoid unnecessary (re)imports in main program
    name, seconds = tuple(args)   # unpack args-list here
    sleep(seconds)
    print('Hi', name)

...现在可以这样称呼:

hello([['Marty', 0.5],
       ['Catherine', 0.9],
       ['Tyler', 0.7],
       ['Pavel', 0.3]])

...输出:

This is the parallelized function:
 def func(args):
        from time import sleep
        name, seconds = tuple(args)
        sleep(seconds)
        print('Hi', name)
Launching processes...
Hi Pavel
Hi Marty
Hi Tyler
Hi Catherine

感谢阅读,这是我的第一篇文章。如果发现任何错误或不良做法,请随时发表评论。我知道这些字符串转换很脏,但是...

答案 2 :(得分:0)

在多处理中使用装饰器时,我也遇到了一些问题。我不确定这是否与您的问题相同

我的代码如下:

from multiprocessing import Pool

def decorate_func(f):
    def _decorate_func(*args, **kwargs):
        print "I'm decorating"
        return f(*args, **kwargs)
    return _decorate_func

@decorate_func
def actual_func(x):
    return x ** 2

my_swimming_pool = Pool()
result = my_swimming_pool.apply_async(actual_func,(2,))
print result.get()

当我运行代码时,我得到了:

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    print result.get()
  File "somedirectory_too_lengthy_to_put_here/lib/python2.7/multiprocessing/pool.py", line 572, in get
    raise self._value
cPickle.PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

我通过定义一个将函数包装在装饰器函数中而不是使用装饰器语法的函数来修复了该问题

from multiprocessing import Pool

def decorate_func(f):
    def _decorate_func(*args, **kwargs):
        print "I'm decorating"
        return f(*args, **kwargs)
    return _decorate_func

def actual_func(x):
    return x ** 2

def wrapped_func(*args, **kwargs):
    return decorate_func(actual_func)(*args, **kwargs)

my_swimming_pool = Pool()
result = my_swimming_pool.apply_async(wrapped_func,(2,))
print result.get()

代码运行完美,我得到了:

I'm decorating
4

我对Python不太了解,但是此解决方案为我解决了我的问题