다중 처리 Pool.map ()을 사용할 때 <type 'instancemethod'>을 (를) 피클 할 수 없습니다


218

작업을 동시에 나누기 위해 multiprocessingPool.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: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed

다음이 내 주요 프로그램 인 경우에 발생합니다.

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

누구나 문제가 무엇인지, 쉽게 해결할 수 있는지 알고 있습니까?


4
f가 중첩 함수이면 비슷한 오류가 발생합니다PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg

답변:


122

문제는 멀티 프로세싱이 프로세스간에 슬링하기 위해 피클을 처리해야하며 바인딩 된 메서드는 피클 링 할 수 없다는 것입니다. 해결 방법 ( "쉬운"여부와 상관없이)은 프로그램에 인프라를 추가하여 그러한 메소드를 피클 할 수있게하고 copy_reg 표준 라이브러리 메소드에 등록하는 것입니다 .

예를 들어, 스레드 의 끝까지이 스레드에 대한 Steven Bethard의 기여는를 통해 메소드 산 세척 / 언 피클 링을 수행 할 수있는 완벽하게 실행 가능한 방법 중 하나를 보여줍니다 copy_reg.


대단해-고마워 어쨌든 어떤 방식 으로든 진행된 것 같습니다. pastebin.ca/1693348 의 코드 사용 이제 RuntimeError : 최대 재귀 깊이를 초과했습니다. 나는 주변을 둘러 보았고 포럼 게시물 하나에서 최대 깊이를 1500 (기본값 1000에서)으로 늘리는 것이 좋지만 기쁨은 없었습니다. 솔직히 말해서, 어떤 이유로 코드를 만들기 위해 약간의 변경으로 인해 루프에서 산세 및 절임이 아닌 한 (내 코드의 적어도) 제어 할 수없는 부분을 볼 수 없습니다. 스티븐의 코드가 OO'd?
ventolin

1
당신의 _pickle_method반환 self._unpickle_method, 바운드 메서드; 물론 pickle은 이제 그것을 피클하려고 시도합니다. 그리고 당신이 말한대로 : _pickle_method재귀 적 으로 호출하여 수행 합니다. 즉 OO, 이런 식으로 코드를 작성하면 필연적으로 무한 재귀가 도입되었습니다. 나는 Steven의 코드로 돌아가는 것이 좋습니다 (그리고 적절하지 않을 때 OO의 제단에서 예배하지 마십시오 : 파이썬의 많은 것들이 더 기능적인 방법으로 가장 잘 이루어지며 이것이 하나입니다).
Alex Martelli


15
슈퍼 슈퍼 게으른 에 대한 실제 엉망 코드를 게시
귀찮은

2
산세 문제를 해결 / 우회하는 또 다른 방법은 딜을 사용하는 것입니다. 내 대답을보십시오 stackoverflow.com/questions/8804830/…
rocksportrocker

74

표준 라이브러리를 벗어나지 않으면 멀티 프로세싱 및 피클 링이 중단되고 제한되기 때문에 이러한 솔루션은 모두보기 흉한 상태입니다.

당신의 포크를 사용하는 경우 multiprocessing전화를 pathos.multiprocesssing직접의 멀티 프로세싱에 클래스와 클래스 메소드를 사용할 수있는 map기능. 이 때문입니다 dill대신 사용 pickle하거나 cPickle, 및 dill파이썬에서 거의 모든 것을 직렬화 할 수 있습니다.

pathos.multiprocessing또한 비동기지도 기능을 제공합니다 ... 그리고 수 map(예 : 여러 인수 기능 map(math.pow, [1,2,3], [4,5,6]))

참조 : 어떤 멀티 프로세싱 할 수 있고 함께 할 딜?

및 : http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/

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

그리고 명백하게, 당신은 당신이 처음에하고 싶었던 것을 정확하게 원할 수 있고, 원한다면 통역사로부터 그것을 할 수 있습니다.

>>> 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 에서 코드를 얻으십시오.


3
pathos.multiprocessing이 더 이상 존재하지 않으므로 pathos.pp를 기반으로이 답변을 업데이트 할 수 있습니까?
Saheel Godhane

10
나는 야 pathos작가. 언급 한 버전이 몇 년 전입니다. github에서 버전을 사용해보십시오 . pathos.pp또는 github.com/uqfoundation/ppft를 사용할 수 있습니다 .
Mike McKerns

1
또는 github.com/uqfoundation/pathos 입니다. @SaheelGodhane : 새 버전은 오래되었지만 곧 나올 것입니다.
Mike McKerns

3
먼저 pip install setuptools, 다음 pip install git+https://github.com/uqfoundation/pathos.git@master. 이것은 적절한 의존성을 얻습니다. 새로운 릴리즈가 거의 준비되었습니다. 이제 거의 모든 것이 pathosWindows에서도 실행되며 3.x호환됩니다.
Mike McKerns

1
@Rika : 그렇습니다. 블로킹, 반복 및 비동기 맵을 사용할 수 있습니다.
Mike McKerns

35

__call__()내부 에 메소드를 정의 할 수도 있습니다.이 메소드 someClass()를 호출 someClass.go()한 다음 인스턴스를 someClass()풀에 전달합니다 . 이 객체는 피클 가능하며 잘 작동합니다 ...


3
이것은 Alex Martelli이 제안한 기술보다 훨씬 쉽지만 클래스 당 하나의 메소드 만 멀티 프로세싱 풀로 보내는 것으로 제한됩니다.
더 이상 사용되지 않음

6
명심해야 할 또 다른 세부 사항 은 클래스 자체가 아니라 절인 객체 (클래스 인스턴스) 뿐이 라는 것입니다. 따라서 클래스 속성을 기본값에서 변경 한 경우 이러한 변경 사항이 다른 프로세스로 전파되지 않습니다. 해결 방법은 함수에 필요한 모든 것이 인스턴스 속성으로 저장되도록하는 것입니다.
더 이상 사용되지 않음

2
@dorvak 간단한 예제를 보여 주 __call__()시겠습니까? 나는 당신의 대답이 더 깨끗한 것이라고 생각합니다.이 오류를 이해하고 전화를 처음 보게 될 때 고심하고 있습니다. 그건 그렇고,이 답변은 멀티 프로세싱이 무엇을하는지 명확하게하는 데 도움이됩니다 : [ stackoverflow.com/a/20789937/305883]
user305883

1
이것의 예를 들어 줄 수 있습니까?
frmsaul

1
이에 대한 예제 코드와 함께 (현재 아래에) 새로운 답변이 게시되었습니다.
Aaron

22

Steven Bethard의 솔루션에는 몇 가지 제한 사항이 있습니다.

클래스 메소드를 함수로 등록하면 메소드 처리가 완료 될 때마다 클래스의 소멸자가 놀랍게도 호출됩니다. 따라서 메소드의 n 배를 호출하는 클래스의 인스턴스가 1 회있는 경우 멤버는 2 번 실행 사이에 사라지고 메시지 malloc: *** error for object 0x...: pointer being freed was not allocated(예 : 열린 멤버 파일) 또는 pure virtual method called, terminate called without an active exception(사용 한 멤버 객체의 수명 이보다 짧았습니다. 내가 생각한 것). 수영장 크기보다 큰 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

__call__[없음 ...] 결과로부터 판독되지 않으므로 방법은 매우 동등하지 않다 :

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 !

따라서 두 방법 중 어느 것도 만족스럽지 않습니다 ...


7
당신은 얻기 None의 당신의 정의 때문에 다시 __call__누락이 return이 같아야합니다 return self.process_obj(i).
torek

1
@Eric 나는 같은 오류가 발생 하여이 솔루션을 시도했지만 "cPickle.PicklingError : <type 'function'>을 피클 할 수 없습니다 : attribute lookup builtin .function failed" 와 같은 새로운 오류가 발생하기 시작 했습니다. 그 뒤에 가능한 이유가 무엇인지 알고 있습니까?
Naman

15

클래스 인스턴스의 내용에 따라 비효율적 일 수 있지만 사용할 수있는 또 다른 바로 가기가 있습니다.

모든 사람들이 말했듯이, multiprocessing코드는 코드가 시작한 서브 프로세스로 전송하는 것을 피클해야하고 피커는 인스턴스 메소드를 수행하지 않는다는 것입니다.

그러나 인스턴스 메소드를 전송하는 대신 실제 클래스 인스턴스와 호출 할 함수의 이름을 getattr인스턴스 메소드를 호출하는 데 사용되는 일반 함수 로 전송하여 Pool서브 프로세스 에서 바운드 메소드를 작성할 수 있습니다. 이것은 __call__둘 이상의 멤버 함수를 호출 할 수 있다는 점을 제외하고 메소드 정의와 유사 합니다.

@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 번, 원본에서 한 번) 호출된다는 것을 보여줍니다 방법). 기본 피커는 전체 인스턴스의 복사본을 만들고 (반)이 비밀리에 다시 채우기 때문에이 경우처럼 다음과 같이 수행하는 것이 좋습니다.

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

그렇기 때문에 3 개의 작업자 프로세스에서 소멸자가 8 번 호출 되더라도 매번 1에서 0으로 카운트 다운됩니다. 물론 이러한 방식으로 문제가 발생할 수 있습니다. 필요한 경우 자신의 것을 제공 할 수 있습니다 __setstate__.

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

이 경우 예를 들어.


1
피클 링이 불가능한 기본 동작에 적용하는 것이 가장 쉽기 때문에이 문제에 대한 최상의 답변입니다
Matt Taylor

12

__call__()내부 에 메소드를 정의 할 수도 있습니다.이 메소드 someClass()를 호출 someClass.go()한 다음 인스턴스를 someClass()풀에 전달합니다 . 이 객체는 피클 가능하며 잘 작동합니다 ...

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

3

위의 parisjohn 의 솔루션은 나와 잘 작동합니다. 또한 코드가 깨끗하고 이해하기 쉽습니다. 필자의 경우 Pool을 사용하여 호출하는 함수가 몇 가지 있으므로 parisjohn의 코드를 약간 아래에서 수정했습니다. 내가 만든 전화 몇 가지 함수를 호출 할 수 있도록하고, 함수 이름에서 인수 DICT에 전달됩니다 go():

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

1

이것에 대한 잠재적 인 사소한 해결책은을로 전환하는 것 multiprocessing.dummy입니다. 이것은 파이썬 2.7 에서이 문제가없는 것으로 보이는 멀티 프로세싱 인터페이스의 스레드 기반 구현입니다. 여기에 많은 경험이 없지만이 빠른 가져 오기 변경으로 클래스 메소드에서 apply_async를 호출 할 수있었습니다.

에 대한 몇 가지 좋은 자료 multiprocessing.dummy:

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

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


1

이 간단한 경우 someClass.f클래스에서 데이터를 상속하지 않고 클래스에 아무것도 첨부하지 않는 경우 가능한 해결책은 분리하여 f피클 할 수 있습니다.

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

1

별도의 기능을 사용하지 않는 이유는 무엇입니까?

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

print pool.map(func, arr)

1

나는이 같은 문제에 부딪 쳤지 만 프로세스간에 이러한 객체를 이동하는 데 사용할 수있는 JSON 인코더가 있음을 알았습니다.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

이것을 사용하여 목록을 만드십시오.

jsonSerialized = json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

그런 다음 맵핑 된 함수에서이를 사용하여 오브젝트를 복구하십시오.

pfVmomiObj = json.loads(jsonSerialized)

0

업데이트 :이 글을 쓰는 시점에서 namedTuples를 선택할 수 있습니다 (파이썬 2.7로 시작)

여기서 문제는 하위 프로세스가 객체의 클래스를 가져올 수 없다는 것입니다.이 경우 클래스 P는 다중 모델 프로젝트의 경우 하위 프로세스가 사용되는 모든 위치에서 클래스 P를 가져올 수 있어야합니다

빠른 해결 방법은 globals ()에 영향을 주어 가져 오기 가능하게 만드는 것입니다.

globals()["P"] = P
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.