Impossibile decapare <type 'instancemethod'> quando si utilizza il multiprocessing Pool.map ()


218

Sto cercando di usare quello multiprocessingdiPool.map() funzione per dividere il lavoro contemporaneamente. Quando uso il seguente codice, funziona perfettamente:

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

Tuttavia, quando lo uso in un approccio più orientato agli oggetti, non funziona. Il messaggio di errore che fornisce è:

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

Ciò si verifica quando il seguente è il mio programma principale:

import someClass

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

e la seguente è la mia someClassclasse:

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

Qualcuno sa quale potrebbe essere il problema o un modo semplice per aggirare il problema?


4
se f è una funzione nidificata, si verifica un errore similePicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg,

Risposte:


122

Il problema è che il multiprocessing deve mettere in ordine le cose per metterle tra i processi e i metodi associati non sono selezionabili. La soluzione alternativa (sia che lo consideri "facile" o meno ;-) è aggiungere l'infrastruttura al programma per consentire il decapaggio di tali metodi, registrandolo con il metodo di libreria standard copy_reg .

Ad esempio, il contributo di Steven Bethard a questo thread (verso la fine del thread) mostra un approccio perfettamente praticabile per consentire il metodo di decapaggio / disimballaggio tramite copy_reg.


È fantastico, grazie. Sembra aver progredito in qualche modo, comunque: usando il codice su pastebin.ca/1693348 ora ottengo un RuntimeError: superata la profondità massima di ricorsione. Mi sono guardato intorno e un post sul forum mi ha consigliato di aumentare la profondità massima a 1500 (dai 1000 predefiniti) ma non ho avuto gioia lì. Ad essere sincero, non riesco a vedere quale parte (del mio codice, almeno) potrebbe ricorrere fuori controllo, a meno che per qualche motivo il codice si stacchi e si stacchi in un ciclo, a causa di lievi modifiche che ho fatto al fine di apportare Il codice di Steven OO'd?
Ventolina,

1
I tuoi _pickle_methodresi self._unpickle_method, un metodo vincolato; quindi ovviamente il sottaceto ora prova a decapare QUELLO - e fa come gli hai detto: chiamando _pickle_method, ricorsivamente. Cioè OOinserendo il codice in questo modo, hai inevitabilmente introdotto una ricorsione infinita. Suggerisco di tornare al codice di Steven (e non adorare all'altare di OO quando non è appropriato: molte cose in Python sono fatte meglio in un modo più funzionale, e questo è uno).
Alex Martelli,


15
Per il super super pigro , vedi l'unica risposta che si è preoccupata di pubblicare il vero codice non maledetto ...
Cerin,

2
Un altro modo per risolvere / aggirare il problema decapaggio sta usando aneto, vedere la mia risposta stackoverflow.com/questions/8804830/...
rocksportrocker

74

Tutte queste soluzioni sono brutte perché il multiprocessing e il decapaggio sono rotti e limitati a meno che non si salti fuori dalla libreria standard.

Se si utilizza un fork di multiprocessingchiamato pathos.multiprocesssing, è possibile utilizzare direttamente classi e metodi di classe nelle mapfunzioni di multiprocessing . Questo perché dillviene utilizzato al posto di pickleo cPicklee dillpuò serializzare quasi tutto in Python.

pathos.multiprocessingfornisce anche una funzione di mappa asincrona ... e può mapfunzionare con più argomenti (ad es. map(math.pow, [1,2,3], [4,5,6]))

Vedi: Cosa possono fare insieme il multiprocessing e l'aneto?

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

E solo per essere espliciti, puoi fare esattamente quello che volevi fare in primo luogo, e puoi farlo dall'interprete, se lo desideri.

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

Ottieni il codice qui: https://github.com/uqfoundation/pathos


3
Potete per favore aggiornare questa risposta in base a pathos.pp perché pathos.multiprocessing non esiste più?
Saheel Godhane,

10
Sono l' pathosautore La versione a cui ti riferisci ha diversi anni. Prova la versione su github, puoi usare pathos.ppo github.com/uqfoundation/ppft .
Mike McKerns,

1
o github.com/uqfoundation/pathos . @SaheelGodhane: una nuova versione è attesa da tempo, ma dovrebbe essere presto disponibile.
Mike McKerns,

3
Prima pip install setuptoolsallora pip install git+https://github.com/uqfoundation/pathos.git@master. Ciò otterrà le dipendenze appropriate. Una nuova versione è quasi pronta ... ora quasi tutto pathosfunziona anche su Windows ed è 3.xcompatibile.
Mike McKerns,

1
@Rika: Sì. sono disponibili mappe di blocco, iterative e asincrone.
Mike McKerns,

35

È inoltre possibile definire un __call__()metodo all'interno di your someClass(), che chiama someClass.go()e quindi passa un'istanza someClass()al pool. Questo oggetto è selezionabile e funziona benissimo (per me) ...


3
Questo è molto più semplice della tecnica proposta da Alex Martelli, ma sei limitato a inviare un solo metodo per classe al tuo pool multiprocessore.
deprecato

6
Un altro dettaglio da tenere a mente è che è solo l'oggetto (istanza di classe) che viene decapitato, non la classe stessa. Pertanto, se hai modificato gli attributi di classe dai loro valori predefiniti, queste modifiche non si propagheranno ai diversi processi. La soluzione alternativa è assicurarsi che tutto ciò di cui la tua funzione ha bisogno sia memorizzata come attributo di istanza.
deprecato

2
@dorvak potresti mostrare un semplice esempio con __call__()? Penso che la tua risposta potrebbe essere la più pulita - sto lottando per capire questo errore e la prima volta che vengo a vedere la chiamata. A proposito, anche questa risposta aiuta a chiarire che cosa fa il multiprocessing: [
stackoverflow.com/a/20789937/305883

1
Puoi fare un esempio di questo?
frmsaul,

1
È stata pubblicata una nuova risposta (attualmente sotto questa) con un codice di esempio per questo.
Aaron

22

Alcune limitazioni alla soluzione di Steven Bethard:

Quando registri il tuo metodo di classe come funzione, il distruttore della tua classe viene sorprendentemente chiamato ogni volta che l'elaborazione del metodo è terminata. Quindi, se hai 1 istanza della tua classe che chiama n volte il suo metodo, i membri potrebbero scomparire tra 2 esecuzioni e potresti ricevere un messaggio malloc: *** error for object 0x...: pointer being freed was not allocated(ad es. File membro aperto) o pure virtual method called, terminate called without an active exception(il che significa che la durata di un oggetto membro che ho usato era inferiore a Ciò che ho pensato). Ho capito quando ho a che fare con n maggiore della dimensione del pool. Ecco un breve esempio:

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)

Produzione:

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

Il __call__metodo non è così equivalente, perché [Nessuno, ...] viene letto dai risultati:

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 !

Quindi nessuno dei due metodi è soddisfacente ...


7
Si Noneritorna perché nella definizione di __call__manca il return: dovrebbe essere return self.process_obj(i).
Torek,

1
@Eric Stavo ottenendo lo stesso errore e ho provato questa soluzione, tuttavia ho iniziato a ricevere un nuovo errore come "cPickle.PicklingError: impossibile decapare <type 'function'>: ricerca degli attributi builtin .function non riuscita". Sai quale può essere una probabile ragione dietro?
Naman

15

C'è un'altra scorciatoia che puoi usare, anche se può essere inefficiente a seconda di cosa c'è nelle tue istanze di classe.

Come tutti hanno detto, il problema è che il multiprocessingcodice deve decapare le cose che invia ai sottoprocessi che ha avviato e il pickler non esegue metodi di istanza.

Tuttavia, invece di inviare il metodo di istanza, è possibile inviare l'istanza di classe effettiva, oltre al nome della funzione da chiamare, a una funzione ordinaria che quindi utilizza getattrper chiamare il metodo di istanza, creando così il metodo associato nel Poolsottoprocesso. Ciò è simile alla definizione di un __call__metodo, tranne per il fatto che è possibile chiamare più di una funzione membro.

Rubare il codice di @ EricH. dalla sua risposta e annotarlo un po '(l'ho scritto di nuovo, quindi tutti i nomi cambiano e così, per qualche motivo questo sembrava più facile del taglia e incolla :-)) per l'illustrazione di tutta la magia:

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)

L'output mostra che, in effetti, il costruttore viene chiamato una volta (nel pid originale) e il distruttore viene chiamato 9 volte (una volta per ogni copia effettuata = 2 o 3 volte per processo di pool-worker secondo necessità, più una volta nell'originale processi). Questo è spesso OK, come in questo caso, poiché il pickler predefinito crea una copia dell'intera istanza e (semi) ripopola segretamente, in questo caso, facendo:

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

— Ecco perché anche se il distruttore viene chiamato otto volte nei tre processi di lavoro, conta da 1 a 0 ogni volta — ma ovviamente puoi ancora metterti nei guai in questo modo. Se necessario, puoi fornire il tuo __setstate__:

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

in questo caso ad esempio.


1
Questa è di gran lunga la migliore risposta a questo problema, in quanto è la più semplice da applicare al comportamento predefinito non in grado di decapare
Matt Taylor,

12

È inoltre possibile definire un __call__()metodo all'interno di your someClass(), che chiama someClass.go()e quindi passa un'istanza someClass()al pool. Questo oggetto è selezionabile e funziona benissimo (per me) ...

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

La soluzione di Parigi di cui sopra funziona bene con me. Inoltre il codice sembra pulito e di facile comprensione. Nel mio caso ci sono alcune funzioni da chiamare usando Pool, quindi ho modificato il codice di Parigi un po 'di seguito. Ho chiamato per essere in grado di chiamare diverse funzioni e i nomi delle funzioni sono passati nell'argomento dict da 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

Una soluzione potenzialmente banale a questo è passare all'utilizzo multiprocessing.dummy. Questa è un'implementazione basata su thread dell'interfaccia multiprocessing che non sembra avere questo problema in Python 2.7. Non ho molta esperienza qui, ma questa rapida modifica dell'importazione mi ha permesso di chiamare apply_async con un metodo di classe.

Alcune buone risorse su multiprocessing.dummy:

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

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


1

In questo semplice caso, in cui someClass.fnon si ereditano dati dalla classe e non si allega nulla alla classe, una possibile soluzione sarebbe quella di separare f, quindi può essere decapato:

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

Perché non usare funzioni separate?

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

print pool.map(func, arr)

1

Ho riscontrato questo stesso problema, ma ho scoperto che esiste un codificatore JSON che può essere utilizzato per spostare questi oggetti tra i processi.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Usa questo per creare il tuo elenco:

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

Quindi nella funzione mappata, utilizzalo per recuperare l'oggetto:

pfVmomiObj = json.loads(jsonSerialized)

0

Aggiornamento: a partire dal giorno in cui scrivo, le coppie nominate sono selezionabili (a partire da Python 2.7)

Il problema qui è che i processi figlio non sono in grado di importare la classe dell'oggetto-in questo caso, la classe P-, nel caso di un progetto multi-modello la Classe P dovrebbe essere impacchettabile ovunque venga usato il processo figlio

una soluzione rapida è renderlo improprio interessandolo a globals ()

globals()["P"] = P
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.