Python Can';t pickle<;类型';instancemethod'&燃气轮机;使用多处理池时。map()

Python Can';t pickle<;类型';instancemethod'&燃气轮机;使用多处理池时。map(),python,multithreading,multiprocessing,pickle,pool,Python,Multithreading,Multiprocessing,Pickle,Pool,我正在尝试使用多处理的Pool.map()函数同时划分工作。当我使用以下代码时,它工作正常: 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() 然而,当我在更面向对象的方法中使用它

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

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()
然而,当我在更面向对象的方法中使用它时,它不起作用。它给出的错误消息是:

PicklingError:无法pickle:属性查找
__内置实例方法失败
当以下是我的主程序时会发生这种情况:

import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()
下面是我的
someClass
课程:

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处理,以便在进程之间吊起它们,而绑定方法是不可拾取的。解决方法(不管你认为它是否简单),是将基础设施添加到程序中,允许这种方法被腌制,用标准库方法注册。
例如,Steven Bethard对(在线程末尾)的贡献显示了一种非常可行的方法,允许通过
copy\u reg

对方法进行酸洗/取消酸洗,您还可以在
someClass()
中定义一个
\u call\u()
方法,该方法调用
someClass.go()
然后将
someClass()的实例传递给池。此对象是可酸洗的,并且(对我而言)工作正常。

尽管Steven Bethard的解决方案存在一些限制:

将类方法注册为函数时,每次方法处理完成时都会意外地调用类的析构函数。因此,如果您的类有一个实例调用其方法n次,则成员可能在两次运行之间消失,您可能会收到一条消息
malloc:**object 0x的错误…:未分配要释放的指针(例如,打开成员文件)或调用了
纯虚拟方法,
在没有活动异常的情况下调用terminate
(这意味着我使用的成员对象的生存期比我想象的要短)。我在处理大于池大小的n时得到了这个。下面是一个简短的例子:

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)
输出:

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
\uuuu调用\uuuu
方法不是那么等效,因为从结果中读取[None,…]:

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 !

因此,这两种方法都不能令人满意……

您可以使用另一条捷径,尽管它可能效率低下,具体取决于类实例中的内容

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

但是,您可以不发送实例方法,而是将实际的类实例以及要调用的函数的名称发送到普通函数,然后该函数使用
getattr
调用实例方法,从而在
池中创建绑定方法。这类似于定义一个
\uuuu调用\uuuu
方法,只是您可以调用多个成员函数

从他的答案中盗取@EricH.的代码并稍加注释(我重新键入了它,因此所有的名称都发生了变化,出于某种原因,这似乎比剪切粘贴更容易:-)来说明所有的魔力:

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生成整个实例的副本,并在本例中(半)秘密地重新填充它,执行以下操作:

obj = object.__new__(Klass)
obj.__dict__.update({'count':1})
-这就是为什么尽管析构函数在三个工作进程中被调用了八次,但每次它都从1倒计时到0,当然,这样你仍然会遇到麻烦。如有必要,您可以提供自己的
\uuuu设置状态\uuu

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

例如,在这种情况下。

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

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

pathos.multi-processing
还提供了一个异步映射函数…它可以
map
具有多个参数的函数(例如
map(math.pow,[1,2,3],[4,5,6])

见:

以及:

明确地说,你可以做你想做的事情,如果你想做的话,你可以通过解释器来做

>>> 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]
>>> 
在此处获取代码:
一个可能很简单的解决方案是切换到使用
多处理.dummy
。这是一个基于线程的多处理接口实现,在Python2.7中似乎没有这个问题。我在这里没有太多的经验,但是这个快速的导入更改允许我在类方法上调用apply\u async

关于
多处理的一些好资源。dummy


您还可以在
someClass()
中定义一个
\uuuu调用
方法,该方法调用
someClass.go()
,然后将
someClass()
的实例传递到池中。这个东西是可以腌制的,对我来说很好


在这种简单的情况下,如果
someClass.f
没有从类继承任何数据,也没有向类附加任何内容,一种可能的解决方案是将
f
分离出来,以便对其进行pickle:

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))

为什么不使用单独的func

def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)

更新:截至本文撰写之日,namedtuple是可选择的(从Python2.7开始)

这里的问题是子进程无法
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))
def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)
globals()["P"] = P
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()