it-swarm.com.de

Bei Verwendung von Multiprocessing Pool.map () kann <Typ 'instancemethod'> nicht ausgewählt werden

Ich versuche, die Funktion Pool.map() von multiprocessing zu verwenden, um die Arbeit gleichzeitig aufzuteilen. Wenn ich den folgenden Code verwende, funktioniert es einwandfrei:

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

Wenn ich es jedoch in einem objektorientierteren Ansatz verwende, funktioniert es nicht. Die Fehlermeldung lautet:

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

Dies tritt auf, wenn Folgendes mein Hauptprogramm ist:

import someClass

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

und das folgende ist meine someClass Klasse:

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

Weiß jemand, woran das liegen könnte oder wie man es einfach umgehen kann?

205
ventolin

Das Problem ist, dass Multiprocessing Dinge auswählen muss, um sie zwischen Prozessen zu verschieben, und gebundene Methoden nicht auswählbar sind. Die Problemumgehung (ob Sie es für "einfach" halten oder nicht ;-) besteht darin, die Infrastruktur zu Ihrem Programm hinzuzufügen, damit solche Methoden gebeizt werden können, und sie mit der Standardbibliotheksmethode copy_reg zu registrieren.

Zum Beispiel zeigt Steven Bethards Beitrag zu diesem Thread (gegen Ende des Threads) einen perfekt praktikablen Ansatz, um das Beizen/Entfernen von Methoden über copy_reg.

115
Alex Martelli

Alle diese Lösungen sind hässlich, da die Mehrfachverarbeitung und das Beizen nur eingeschränkt möglich sind, wenn Sie die Standardbibliothek verlassen.

Wenn Sie eine Abzweigung von multiprocessing mit dem Namen pathos.multiprocesssing Verwenden, können Sie Klassen und Klassenmethoden direkt in den map - Funktionen von Multiprocessing verwenden. Dies liegt daran, dass dill anstelle von pickle oder cPickle verwendet wird und dill fast alles in Python serialisieren kann.

pathos.multiprocessing Bietet auch eine asynchrone Zuordnungsfunktion ... und kann map Funktionen mit mehreren Argumenten ausführen (z. B. map(math.pow, [1,2,3], [4,5,6]))

Siehe: Was können Multiprocessing und Dill zusammen tun?

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

Und nur um genau zu sein, Sie können genau das tun, was Sie wollten, und Sie können es vom Interpreter aus tun, wenn Sie wollten.

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

Den Code erhalten Sie hier: https://github.com/uqfoundation/pathos

70
Mike McKerns

Sie können auch eine __call__() -Methode in Ihrer someClass() definieren, die someClass.go() aufruft und dann eine Instanz von someClass() an den Pool übergibt. Dieses Objekt ist pickelbar und es funktioniert gut (für mich) ...

33
dorvak

Einige Einschränkungen für Steven Bethards Lösung:

Wenn Sie Ihre Klassenmethode als Funktion registrieren, wird der Destruktor Ihrer Klasse überraschenderweise jedes Mal aufgerufen, wenn Ihre Methodenverarbeitung abgeschlossen ist. Wenn Sie also eine Instanz Ihrer Klasse haben, die die n-fache Methode aufruft, verschwinden die Mitglieder möglicherweise zwischen zwei Durchläufen, und Sie erhalten möglicherweise eine Meldung malloc: *** error for object 0x...: pointer being freed was not allocated (z. B. offene Mitgliedsdatei) oder pure virtual method called, terminate called without an active exception (was bedeutet, dass die Lebensdauer eines Mitgliedsobjekts, das ich verwendet habe, kürzer war als gedacht). Ich habe dies beim Umgang mit n größer als die Poolgröße. Hier ist ein kurzes Beispiel:

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)

Ausgabe:

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

Das __call__ Methode ist nicht so äquivalent, weil [None, ...] aus den Ergebnissen gelesen werden:

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 !

Keine der beiden Methoden ist also zufriedenstellend ...

20
Eric H.

Es gibt noch eine andere Abkürzung, die Sie verwenden können, obwohl sie je nach den Klasseninstanzen ineffizient sein kann.

Wie jeder gesagt hat, besteht das Problem darin, dass der Code multiprocessing die Dinge auswählen muss, die er an die von ihm gestarteten Unterprozesse sendet, und dass der Pickler keine Instanzmethoden ausführt.

Anstatt jedoch die Instanzmethode zu senden, können Sie die tatsächliche Klasseninstanz und den Namen der aufzurufenden Funktion an eine normale Funktion senden, die dann getattr verwendet, um die Instanzmethode aufzurufen gebundene Methode im Unterprozess Pool. Dies ähnelt der Definition eines __call__ -Methode mit der Ausnahme, dass Sie mehr als eine Mitgliedsfunktion aufrufen können.

Stehlen Sie den Code von @ EricH. Aus seiner Antwort und kommentieren Sie ihn ein wenig (ich habe ihn erneut getippt, daher ändert sich der Name und so, aus irgendeinem Grund schien dies einfacher als Ausschneiden und Einfügen :-)), um die ganze Magie zu veranschaulichen:

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)

Die Ausgabe zeigt, dass der Konstruktor in der Tat einmal (in der Original-PID) und der Destruktor neunmal (je nach Bedarf einmal für jede angefertigte Kopie = zwei- oder dreimal pro Pool-Worker-Prozess plus einmal im Original) aufgerufen wird verarbeiten). Dies ist wie in diesem Fall häufig in Ordnung, da der Standard-Pickler eine Kopie der gesamten Instanz erstellt und diese (halb-) geheim neu auffüllt. In diesem Fall gehen Sie folgendermaßen vor:

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

Das ist der Grund, warum der Destruktor in den drei Arbeitsprozessen zwar achtmal aufgerufen wird, aber jedes Mal von 1 auf 0 herunterzählt. Aber auf diese Weise können Sie natürlich immer noch in Schwierigkeiten geraten. Bei Bedarf können Sie Ihr eigenes __setstate__:

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

in diesem Fall zum Beispiel.

14
torek

Sie können auch eine __call__() -Methode in Ihrer someClass() definieren, die someClass.go() aufruft und dann eine Instanz von someClass() an den Pool übergibt. Dieses Objekt ist pickelbar und es funktioniert gut (für mich) ...

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

Die Lösung von parisjohn oben funktioniert gut bei mir. Außerdem sieht der Code sauber und leicht zu verstehen aus. In meinem Fall gibt es ein paar Funktionen, die mit Pool aufgerufen werden können. Deshalb habe ich den Code von parisjohn ein wenig weiter unten geändert. Ich habe call gemacht, um mehrere Funktionen aufrufen zu können, und die Funktionsnamen werden im Argument dict von go() übergeben:

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

Warum nicht eine separate Funktion verwenden?

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

print pool.map(func, arr)
1
0script0

Ich bin auf dasselbe Problem gestoßen, habe jedoch festgestellt, dass es einen JSON-Encoder gibt, mit dem diese Objekte zwischen Prozessen verschoben werden können.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Verwenden Sie dies, um Ihre Liste zu erstellen: jsonSerialized= json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

Verwenden Sie dann in der zugeordneten Funktion diesen Befehl, um das Objekt wiederherzustellen: pfVmomiObj = json.loads(jsonSerialized)

1
George

Eine möglicherweise triviale Lösung hierfür ist die Verwendung von multiprocessing.dummy. Dies ist eine Thread-basierte Implementierung der Multiprocessing-Oberfläche, die dieses Problem in Python 2.7. Ich habe hier nicht viel Erfahrung, aber diese schnelle Importänderung hat es mir erlaubt um apply_async für eine Klassenmethode aufzurufen.

Ein paar gute Ressourcen zu multiprocessing.dummy:

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

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

1
David Parks

In diesem einfachen Fall, in dem someClass.f erbt keine Daten von der Klasse und hängt nichts an die Klasse an. Eine mögliche Lösung wäre, f abzutrennen, damit es gebeizt werden kann:

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
mhh

Update: Ab dem heutigen Tag können namedTuples ausgewählt werden (beginnend mit python 2.7)

Das Problem hierbei ist, dass die untergeordneten Prozesse die Klasse des Objekts nicht importieren können - in diesem Fall die Klasse P -. Im Fall eines Projekts mit mehreren Modellen sollte die Klasse P überall dort importierbar sein, wo der untergeordnete Prozess verwendet wird

eine schnelle Problemumgehung besteht darin, es durch Beeinflussung von Globals () importierbar zu machen.

globals()["P"] = P
0