关于python:不能pickle< type’instancemethod’>

Can't pickle <type 'instancemethod'> when using multiprocessing Pool.map()

我尝试使用multiprocessingPool.map()函数同时划分工作。当我使用以下代码时,它工作正常:

1
2
3
4
5
6
7
8
9
10
11
12
import multiprocessing

def f(x):
    return x*x

def go():
    pool = multiprocessing.Pool(processes=4)        
    print pool.map(f, range(10))


if __name__== '__main__' :
    go()

但是,当我在更面向对象的方法中使用它时,它不起作用。它给出的错误消息是:

1
2
PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed

当以下是我的主程序时,会发生这种情况:

1
2
3
4
5
import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()

下面是我的someClass课:

1
2
3
4
5
6
7
8
9
10
11
12
import multiprocessing

class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def go(self):
        pool = multiprocessing.Pool(processes=4)      
        print pool.map(self.f, range(10))

有人知道问题可能是什么,还是一种简单的解决方法?


问题在于,多处理必须对事物进行pickle处理,以便将它们吊放到进程之间,绑定方法是不可选择的。解决方法(不管您认为它"简单"与否;-)是将基础结构添加到您的程序中,以允许对这些方法进行pickle处理,并使用copy_reg standard library方法对其进行注册。

例如,Steven Bethard对该螺纹的贡献(接近螺纹末端)显示了一种完全可行的方法,允许通过copy_reg进行方法酸洗/拆模。


所有这些解决方案都很难看,因为除非您跳出标准库,否则多处理和酸洗会被破坏和限制。

如果使用一个名为pathos.multiprocesssingmultiprocessing分支,则可以直接在多处理的map函数中使用类和类方法。这是因为使用dill而不是picklecPickle,并且dill几乎可以序列化python中的任何内容。

pathos.multiprocessing还提供了异步映射函数……它可以用多个参数(如map(math.pow, [1,2,3], [4,5,6])来实现map函数。

见:多处理和DILL可以一起做什么?

还有:http://matthewrocklin.com/blog/work/2013/12/05/parallelism-and-serialization/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import pathos.pools as pp
>>> p = pp.ProcessPool(4)
>>>
>>> def add(x,y):
...   return x+y
...
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>>
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>>
>>> class Test(object):
...   def plus(self, x, y):
...     return x+y
...
>>> t = Test()
>>>
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>>
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]

为了明确起见,首先你可以做你想做的事情,如果你想的话,你可以从口译员那里做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> import pathos.pools as pp
>>> class someClass(object):
...   def __init__(self):
...     pass
...   def f(self, x):
...     return x*x
...   def go(self):
...     pool = pp.ProcessPool(4)
...     print pool.map(self.f, range(10))
...
>>> sc = someClass()
>>> sc.go()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>

在此处获取代码:https://github.com/uqfoundation/pathos网站


您还可以在someClass()中定义一个__call__()方法,该方法调用someClass.go(),然后将someClass()的一个实例传递给池。这个物体是可腌制的,它(对我)工作得很好…


但对史蒂文·贝萨德的解决方案有一些限制:

当您将类方法注册为函数时,每次方法处理完成时都会意外地调用类的析构函数。因此,如果您有一个调用n倍方法的类实例,那么成员可能会在两次运行之间消失,并且您可能会收到消息malloc: *** error for object 0x...: pointer being freed was not allocated(例如,open member file)或pure virtual method called,
terminate called without an active exception
(这意味着我使用的成员对象的生存期比我想象的要短)。当处理的n大于池的大小时,我得到了这个。下面是一个简短的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

# --------- see Stenven's solution above -------------
from copy_reg import pickle
from types import MethodType

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)


class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print"Constructor ..."
        # multi-processing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self.process_obj, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __del__(self):
        print"... Destructor"

    def process_obj(self, index):
        print"object %d" % index
        return"results"

pickle(MethodType, _pickle_method, _unpickle_method)
Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Constructor ...
object 0
object 1
object 2
... Destructor
object 3
... Destructor
object 4
... Destructor
object 5
... Destructor
object 6
... Destructor
object 7
... Destructor
... Destructor
... Destructor
['results', 'results', 'results', 'results', 'results', 'results', 'results', 'results']
... Destructor

__call__方法不那么等效,因为[无,…]是从结果中读取的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print"Constructor ..."
        # multiprocessing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __call__(self, i):
        self.process_obj(i)

    def __del__(self):
        print"... Destructor"

    def process_obj(self, i):
        print"obj %d" % i
        return"result"

Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once),
# **and** results are empty !

所以这两种方法都不令人满意…


还有另一个捷径,你可以使用,尽管它可能是低效的,取决于你的类实例。

正如每个人所说的,问题在于multiprocessing代码必须对它发送到已启动的子进程的内容进行pickle处理,而pickler不执行实例方法。

但是,您可以不发送实例方法,而是将实际的类实例加上要调用的函数名发送给一个普通函数,然后使用getattr调用实例方法,从而在Pool子进程中创建绑定方法。这类似于定义__call__方法,只是可以调用多个成员函数。

从@erich的答案中窃取代码并对其进行注释(我重新输入了代码,因此所有名称都发生了更改,出于某种原因,这似乎比剪切和粘贴更容易):)以说明所有的魔法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import multiprocessing
import os

def call_it(instance, name, args=(), kwargs=None):
   "indirect caller for instance methods and multiprocessing"
    if kwargs is None:
        kwargs = {}
    return getattr(instance, name)(*args, **kwargs)

class Klass(object):
    def __init__(self, nobj, workers=multiprocessing.cpu_count()):
        print"Constructor (in pid=%d)..." % os.getpid()
        self.count = 1
        pool = multiprocessing.Pool(processes = workers)
        async_results = [pool.apply_async(call_it,
            args = (self, 'process_obj', (i,))) for i in range(nobj)]
        pool.close()
        map(multiprocessing.pool.ApplyResult.wait, async_results)
        lst_results = [r.get() for r in async_results]
        print lst_results

    def __del__(self):
        self.count -= 1
        print"... Destructor (in pid=%d) count=%d" % (os.getpid(), self.count)

    def process_obj(self, index):
        print"object %d" % index
        return"results"

Klass(nobj=8, workers=3)

输出显示,实际上,构造函数被调用一次(在原始PID中),析构函数被调用9次(根据需要,每个复制调用一次=每个池工作进程2或3次,再加上原始进程中的一次)。这通常是正常的,在这种情况下,因为默认的pickler会复制整个实例并(半)在这种情况下秘密地重新填充它,这样做:

1
2
obj = object.__new__(Klass)
obj.__dict__.update({'count':1})

-这就是为什么即使在三个工作进程中调用了八次析构函数,它每次都从1倒计时到0,但是当然,这样仍然会遇到麻烦。如有必要,您可以提供自己的__setstate__

1
2
    def __setstate__(self, adict):
        self.count = adict['count']

例如,在这种情况下。


您还可以在someClass()中定义一个__call__()方法,该方法调用someClass.go(),然后将someClass()的一个实例传递给池。这个物体是可腌制的,它(对我)工作得很好…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class someClass(object):
   def __init__(self):
       pass
   def f(self, x):
       return x*x

   def go(self):
      p = Pool(4)
      sc = p.map(self, range(4))
      print sc

   def __call__(self, x):  
     return self.f(x)

sc = someClass()
sc.go()


上面的解决方案对我来说很好。另外,代码看起来很干净,很容易理解。在我的例子中,有一些函数可以使用pool调用,所以我在下面稍微修改了parisjohn的代码。我进行了调用,以便能够调用多个函数,函数名在go()的参数dict中传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from multiprocessing import Pool
class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def g(self, x):
        return x*x+1    

    def go(self):
        p = Pool(4)
        sc = p.map(self, [{"func":"f","v": 1}, {"func":"g","v": 2}])
        print sc

    def __call__(self, x):
        if x["func"]=="f":
            return self.f(x["v"])
        if x["func"]=="g":
            return self.g(x["v"])        

sc = someClass()
sc.go()

为什么不使用单独的func?

1
2
3
4
def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)

在这种简单的情况下,如果someClass.f不继承类中的任何数据,也不将任何数据附加到类中,则可能的解决方案是分离f以便进行酸洗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import multiprocessing


def f(x):
    return x*x


class someClass(object):
    def __init__(self):
        pass

    def go(self):
        pool = multiprocessing.Pool(processes=4)      
        print pool.map(f, range(10))


一个可能微不足道的解决方案是切换到使用multiprocessing.dummy。这是多处理接口的基于线程的实现,在Python2.7中似乎没有这个问题。我在这里没有太多的经验,但是这个快速的导入更改允许我对一个类方法调用Apply-Async。

multiprocessing.dummy上的一些好资源:

https://docs.python.org/2/library/multiprocessing.html module-multiprocessing.dummy

http://chriskiehl.com/article/parallelism-in-one-line/


更新:截至本文撰写之日,可以选择namedtuples(从python 2.7开始)

这里的问题是子进程无法导入对象的类(在本例中是类P),在多模型项目的情况下,在使用子进程的任何地方,类P都应该是可导入的

一个快速的解决方法是通过影响Globals()使其具有可导入性。

1
globals()["P"] = P