Can't pickle <type 'instancemethod'> when using multiprocessing Pool.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() |
下面是我的
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对该螺纹的贡献(接近螺纹末端)显示了一种完全可行的方法,允许通过
所有这些解决方案都很难看,因为除非您跳出标准库,否则多处理和酸洗会被破坏和限制。
如果使用一个名为
见:多处理和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网站
您还可以在
但对史蒂文·贝萨德的解决方案有一些限制:
当您将类方法注册为函数时,每次方法处理完成时都会意外地调用类的析构函数。因此,如果您有一个调用n倍方法的类实例,那么成员可能会在两次运行之间消失,并且您可能会收到消息
terminate called without an active exception
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 |
号
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 ! |
所以这两种方法都不令人满意…
还有另一个捷径,你可以使用,尽管它可能是低效的,取决于你的类实例。
正如每个人所说的,问题在于
但是,您可以不发送实例方法,而是将实际的类实例加上要调用的函数名发送给一个普通函数,然后使用
从@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,但是当然,这样仍然会遇到麻烦。如有必要,您可以提供自己的
1 2 | def __setstate__(self, adict): self.count = adict['count'] |
。
例如,在这种情况下。
您还可以在
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的代码。我进行了调用,以便能够调用多个函数,函数名在
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) |
在这种简单的情况下,如果
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)) |
号
一个可能微不足道的解决方案是切换到使用
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 |
。