Python多处理PicklingError:无法腌制<type'function'>


243

很抱歉,我无法使用更简单的示例重现该错误,并且我的代码过于复杂,无法发布。如果我在IPython Shell中而不是在常规Python中运行该程序,那么效果会很好。

我查阅了有关此问题的以前的笔记。它们都是由使用池调用在类函数中定义的函数引起的。但这不是我的情况。

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 313, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

我将不胜感激任何帮助。

更新:我的泡菜功能定义在模块的顶层。虽然它调用包含嵌套函数的函数。即f()要求g()调用h()具有嵌套函数i(),和我打电话pool.apply_async(f)f()g()h()都在顶层定义。我用这种模式尝试了更简单的示例,尽管它可以工作。


3
顶级/可接受的答案是好的,但这可能意味着您需要重新构建代码,这可能会很痛苦。我建议有此问题的任何人也阅读使用dill和的其他答案pathos。但是,在使用vtkobjects时,我对任何解决方案都不满意:(任何人都设法在并行处理vtkPolyData中运行python代码?
Chris

Answers:


305

这是可以腌制的食物清单。特别是,只有在模块的顶层定义了功能时,这些功能才是可腌制的。

这段代码:

import multiprocessing as mp

class Foo():
    @staticmethod
    def work(self):
        pass

if __name__ == '__main__':   
    pool = mp.Pool()
    foo = Foo()
    pool.apply_async(foo.work)
    pool.close()
    pool.join()

产生的错误几乎与您发布的错误相同:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 315, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

问题在于,pool所有方法都使用a mp.SimpleQueue将任务传递给工作进程。mp.SimpleQueue必须经过的所有内容都必须是可选取的,并且foo.work不可选取,因为它不是在模块的顶层定义的。

可以通过在顶层定义一个函数来修复该问题,该函数调用foo.work()

def work(foo):
    foo.work()

pool.apply_async(work,args=(foo,))

请注意,它foo是可拾取的,因为它Foo是在顶层定义的并且 foo.__dict__是可拾取的。


2
感谢您的回复。我更新了我的问题。不过,我不认为这是原因
仇杀队(Vendetta)

7
要获得PicklingError,必须在队列中放置一些不可腌制的东西。它可能是函数或其参数。要查找有关该问题的更多信息,我建议您制作一个程序副本,然后开始对其进行精简,使其变得越来越简单,每次重新运行该程序以查看问题是否仍然存在。当事情变得非常简单时,您将自己发现问题,或者将其发布在此处。
unutbu 2012年

3
另外:如果您在模块的顶层定义了一个函数,但是该函数已经过修饰,则该引用将指向该修饰符的输出,无论如何都会出现此错误。
bobpoekert

5
仅晚了5年,但我刚刚遇到了这个问题。事实证明,“顶级”必须比平时更实际地使用:在我看来,函数定义必须在初始化之前(即,此处pool = Pool()行)。我没想到,这可能是OP问题持续存在的原因。
Andras Deak

4
特别是,只有在模块的顶层定义了功能时,这些功能才是可腌制的。 似乎应用functool.partial到顶层函数的结果也是可腌的,即使它是在另一个函数中定义的也是如此。
user1071847 '19

96

我会用pathos.multiprocesssing,而不是multiprocessingpathos.multiprocessing是一个叉multiprocessing使用dilldill可以在python中序列化几乎所有内容,因此您可以并行发送更多内容。该pathos叉也有直接与多个参数的函数工作的能力,因为你需要为类方法。

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> p = Pool(4)
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]
>>> 
>>> class Foo(object):
...   @staticmethod
...   def work(self, x):
...     return x+1
... 
>>> f = Foo()
>>> p.apipe(f.work, f, 100)
<processing.pool.ApplyResult object at 0x10504f8d0>
>>> res = _
>>> res.get()
101

在此处获取pathos(并且,如果愿意dill):https : //github.com/uqfoundation


5
工作了。对于其他任何人,我都通过以下方式安装了这两个库: sudo pip install git+https://github.com/uqfoundation/dill.git@mastersudo pip install git+https://github.com/uqfoundation/pathos.git@master
Alexander McFarlane 2015年

5
@AlexanderMcFarlane我不会安装python软件包sudo(尤其是从外部源,例如github)。相反,我建议您跑步:pip install --user git+...
克里斯(Chris)

使用just pip install pathos不能令人遗憾地工作,并显示以下消息:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple

11
pip install pathos现在可以使用了,并且与pathospython 3兼容。
Mike McKerns '16

3
@DanielGoldfarb:multiprocess是在代码中的多个位置替换了multiprocessingwhere 的fork ...但实际上,仅此而已。 提供了一些其他API层,并且还具有其他后端。但是,这就是要点。dillpicklepathosmultiprocess
Mike McKerns

29

正如其他人所说multiprocessing,只能将Python对象传输到可以腌制的工作进程。如果您不能按照unutbu的描述重新组织代码,则可以使用dill扩展的酸洗/酸洗功能来传输数据(尤其是代码数据),如下所示。

此解决方案仅需要安装以下dill库,而无需安装其他库pathos

import os
from multiprocessing import Pool

import dill


def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    return fun(*args)


def apply_async(pool, fun, args):
    payload = dill.dumps((fun, args))
    return pool.apply_async(run_dill_encoded, (payload,))


if __name__ == "__main__":

    pool = Pool(processes=5)

    # asyn execution of lambda
    jobs = []
    for i in range(10):
        job = apply_async(pool, lambda a, b: (a, b, a * b), (i, i + 1))
        jobs.append(job)

    for job in jobs:
        print job.get()
    print

    # async execution of static method

    class O(object):

        @staticmethod
        def calc():
            return os.getpid()

    jobs = []
    for i in range(10):
        job = apply_async(pool, O.calc, ())
        jobs.append(job)

    for job in jobs:
        print job.get()

6
我是dilland的pathos作者……而您是对的,难道不是pathos像我的回答那样好用,简洁,灵活得多吗?也许我有点偏见……
Mike McKerns 2014年

4
pathos在撰写本文时还不清楚状态,因此想提出一个非常接近答案的解决方案。现在,我已经看到了您的解决方案,我同意这是要走的路。
rockportrocker 2014年

我读完您的解决方案后, Doh… I didn't even think of doing it like that. 那真是太酷了。
Mike McKerns,2014年

4
感谢张贴,我用这种方法进行迪林/ undilling论据不能腌制:stackoverflow.com/questions/27883574/...
jazzblue

@rocksportrocker。我正在阅读此示例,但无法理解为什么存在显式for循环。我通常会看到并行例程获取一个列表并返回一个没有循环的列表。
user1700890

20

我发现通过尝试在其上使用探查器,我还可以在完美工作的代码段上准确生成该错误输出。

请注意,这是在Windows上进行的(分叉不太优雅)。

我之前在跑步:

python -m profile -o output.pstats <script> 

并发现删除配置文件可以消除错误,并放置配置文件可以恢复错误。我也很生气,因为我知道以前的代码可以工作。我正在检查是否有更新过的pool.py ...然后有下沉的感觉并消除了配置文件,仅此而已。

如果有人遇到,请在此处发布档案。


3
哇,谢谢你的提及!在最后一个小时左右,它使我发疯。我通过一个非常简单的示例尝试了所有操作-似乎没有任何效果。但是我也通过我的批处理文件运行了探查器:(
蒂姆,

1
哦,感激不尽。这听起来确实很愚蠢,因为这太出乎意料了。我认为应该在文档中提及。我所拥有的只是一个导入pdb语句,而只有一个简单的顶层函数pass是不“棘手的”。
0xc0de

10

解决此问题时,multiprocessing一个简单的解决方案是从切换PoolThreadPool。只需导入-

from multiprocessing.pool import ThreadPool as Pool

这是可行的,因为ThreadPool与主线程共享内存,而不是创建新进程-这意味着不需要酸洗。

这种方法的缺点是python并不是处理线程的最佳语言-它使用一种称为Global Interpreter Lock的方法来保持线程安全,这可能会减慢此处的一些用例。但是,如果您主要是与其他系统进行交互(运行HTTP命令,与数据库进行交谈,写入文件系统),则您的代码很可能不受CPU的束缚,因此不会受到太大的影响。实际上,在编写HTTP / HTTPS基准测试时,我发现这里使用的线程模型具有较少的开销和延迟,因为创建新进程的开销比创建新线程的开销高得多。

因此,如果要在python用户空间中处理大量内容,这可能不是最好的方法。


2
但是,此时您仅使用一个CPU(至少与使用GIL的常规Python版本一起使用),这无法达到目的。
Endre双方

这实际上取决于目的。全局解释器锁的确意味着一次只能运行一个实例,但是对于严重阻塞的操作(文件系统访问,下载大文件或多个文件,运行外部代码),GIL最终不是问题。在某些情况下,打开新进程(而不是线程)的开销超过了GIL的开销。
tedivm

是的,谢谢。您仍然可能需要在答案中包括一个警告。如今,处理能力的提高主要以功能更多而不是功能更强大的CPU内核的形式出现,从多核执行切换到单核执行是一个相当重要的副作用。
Endre均为

好点-我已经用更多详细信息更新了答案。我确实要指出,尽管切换到线程多处理并不能使python仅在单个内核上起作用。
tedivm

4

此解决方案仅需要安装莳萝,而无需其他库作为安装程序

def apply_packed_function_for_map((dumped_function, item, args, kwargs),):
    """
    Unpack dumped function as target function and call it with arguments.

    :param (dumped_function, item, args, kwargs):
        a tuple of dumped function and its arguments
    :return:
        result of target function
    """
    target_function = dill.loads(dumped_function)
    res = target_function(item, *args, **kwargs)
    return res


def pack_function_for_map(target_function, items, *args, **kwargs):
    """
    Pack function and arguments to object that can be sent from one
    multiprocessing.Process to another. The main problem is:
        «multiprocessing.Pool.map*» or «apply*»
        cannot use class methods or closures.
    It solves this problem with «dill».
    It works with target function as argument, dumps it («with dill»)
    and returns dumped function with arguments of target function.
    For more performance we dump only target function itself
    and don't dump its arguments.
    How to use (pseudo-code):

        ~>>> import multiprocessing
        ~>>> images = [...]
        ~>>> pool = multiprocessing.Pool(100500)
        ~>>> features = pool.map(
        ~...     *pack_function_for_map(
        ~...         super(Extractor, self).extract_features,
        ~...         images,
        ~...         type='png'
        ~...         **options,
        ~...     )
        ~... )
        ~>>>

    :param target_function:
        function, that you want to execute like  target_function(item, *args, **kwargs).
    :param items:
        list of items for map
    :param args:
        positional arguments for target_function(item, *args, **kwargs)
    :param kwargs:
        named arguments for target_function(item, *args, **kwargs)
    :return: tuple(function_wrapper, dumped_items)
        It returs a tuple with
            * function wrapper, that unpack and call target function;
            * list of packed target function and its' arguments.
    """
    dumped_function = dill.dumps(target_function)
    dumped_items = [(dumped_function, item, args, kwargs) for item in items]
    return apply_packed_function_for_map, dumped_items

它也适用于numpy数组。


2
Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

如果您在传递给异步作业的模型对象中有任何内置函数,也会出现此错误。

因此,请确保检查传递的模型对象没有内置函数。(在我们的例子中,我们使用模型内部的django-model-utilsFieldTracker()函数来跟踪某个字段)。这是相关的GitHub问题的链接


0

建立在@rocksportrocker解决方案的基础上,在发送和接收结果时应该莳萝。

import dill
import itertools
def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    res = fun(*args)
    res = dill.dumps(res)
    return res

def dill_map_async(pool, fun, args_list,
                   as_tuple=True,
                   **kw):
    if as_tuple:
        args_list = ((x,) for x in args_list)

    it = itertools.izip(
        itertools.cycle([fun]),
        args_list)
    it = itertools.imap(dill.dumps, it)
    return pool.map_async(run_dill_encoded, it, **kw)

if __name__ == '__main__':
    import multiprocessing as mp
    import sys,os
    p = mp.Pool(4)
    res = dill_map_async(p, lambda x:[sys.stdout.write('%s\n'%os.getpid()),x][-1],
                  [lambda x:x+1]*10,)
    res = res.get(timeout=100)
    res = map(dill.loads,res)
    print(res)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.