Python multiprocessing PicklingError: Can pickle <type 'function'>


244

Mi dispiace non poter riprodurre l'errore con un esempio più semplice e il mio codice è troppo complicato per essere pubblicato. Se eseguo il programma nella shell IPython invece del normale Python, le cose funzionano bene.

Ho cercato alcune note precedenti su questo problema. Sono stati tutti causati dall'uso del pool per chiamare la funzione definita all'interno di una funzione di classe. Ma questo non è il caso per me.

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 313, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Gradirei qualsiasi aiuto.

Aggiornamento : la funzione I pickle è definita al livello superiore del modulo. Anche se chiama una funzione che contiene una funzione nidificata. vale a dire, f()chiama le g()chiamate h()che hanno una funzione nidificata i()e io sto chiamando pool.apply_async(f). f(), g(), h()Sono tutti definiti a livello superiore. Ho provato un esempio più semplice con questo modello e funziona però.


3
La risposta di livello superiore / accettata è buona, ma potrebbe significare che devi ristrutturare il tuo codice, il che potrebbe essere doloroso. Consiglierei a chiunque abbia questo problema di leggere anche le risposte aggiuntive che utilizzano dille pathos. Tuttavia, non ho fortuna con nessuna delle soluzioni quando si lavora con vtkobjects :( Chiunque è riuscito a eseguire il codice Python nell'elaborazione parallela vtkPolyData?
Chris

Risposte:


306

Ecco un elenco di ciò che può essere decapato . In particolare, le funzioni sono selezionabili solo se sono definite al livello superiore di un modulo.

Questo pezzo di codice:

import multiprocessing as mp

class Foo():
    @staticmethod
    def work(self):
        pass

if __name__ == '__main__':   
    pool = mp.Pool()
    foo = Foo()
    pool.apply_async(foo.work)
    pool.close()
    pool.join()

genera un errore quasi identico a quello che hai pubblicato:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 315, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Il problema è che pooltutti i metodi usano a mp.SimpleQueueper passare le attività ai processi di lavoro. Tutto ciò che passa attraverso mp.SimpleQueuedeve essere selezionabile e foo.worknon è selezionabile poiché non è definito al livello superiore del modulo.

Può essere risolto definendo una funzione di livello superiore, che chiama foo.work():

def work(foo):
    foo.work()

pool.apply_async(work,args=(foo,))

Si noti che fooè selezionabile, poiché Fooè definito al livello più alto ed foo.__dict__è selezionabile.


2
Grazie per la tua risposta. Ho aggiornato la mia domanda. Non credo che questa sia la causa
Vendetta,

7
Per ottenere un PicklingError bisogna mettere qualcosa in coda che non è selezionabile. Potrebbe essere la funzione o i suoi argomenti. Per saperne di più sul problema, ti suggerisco di fare una copia del tuo programma e iniziare a ridimensionarlo, rendendolo sempre più semplice, ogni volta che riesegui il programma per vedere se il problema persiste. Quando diventa davvero semplice, o avrai scoperto tu stesso il problema o avrai qualcosa che puoi pubblicare qui.
unutbu,

3
Inoltre: se definisci una funzione al livello più alto di un modulo, ma è decorata, allora il riferimento sarà all'output del decoratore e otterrai comunque questo errore.
bobpoekert,

5
Solo in ritardo di 5 anni, ma mi sono appena imbattuto in questo. Si scopre che il "livello superiore" deve essere preso più letteralmente del solito: mi sembra che la definizione della funzione debba precedere l' inizializzazione del pool (ovvero la pool = Pool()linea qui ). Non me l'aspettavo, e questo potrebbe essere il motivo per cui il problema di OP persisteva.
Andras Deak

4
In particolare, le funzioni sono selezionabili solo se sono definite al livello superiore di un modulo. Sembra che anche il risultato dell'applicazione functool.partiala una funzione di livello superiore sia inefficace, anche se definito all'interno di un'altra funzione.
user1071847,

96

Userei pathos.multiprocesssing, invece di multiprocessing. pathos.multiprocessingè un fork di multiprocessingciò che usa dill. dillpuoi serializzare quasi tutto in Python, così puoi inviare molto di più in parallelo. Il pathosfork ha anche la capacità di lavorare direttamente con più funzioni di argomento, come è necessario per i metodi di classe.

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> p = Pool(4)
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]
>>> 
>>> class Foo(object):
...   @staticmethod
...   def work(self, x):
...     return x+1
... 
>>> f = Foo()
>>> p.apipe(f.work, f, 100)
<processing.pool.ApplyResult object at 0x10504f8d0>
>>> res = _
>>> res.get()
101

Ottieni pathos(e se vuoi, dill) qui: https://github.com/uqfoundation


5
ha funzionato a meraviglia. Per chiunque altro, ho installato entrambe le librerie attraverso: sudo pip install git+https://github.com/uqfoundation/dill.git@masteresudo pip install git+https://github.com/uqfoundation/pathos.git@master
Alexander McFarlane il

5
@AlexanderMcFarlane Non installerei i pacchetti python con sudo(da fonti esterne come Github in particolare). Invece, consiglierei di eseguire:pip install --user git+...
Chris

L'uso di just pip install pathosnon funziona tristemente e dà questo messaggio:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple

11
pip install pathosora funziona ed pathosè compatibile con Python 3.
Mike McKerns,

3
@DanielGoldfarb: multiprocessè un fork di multiprocessingdove dillè stato sostituito picklein diversi punti del codice ... ma sostanzialmente, tutto qui. pathosfornisce alcuni livelli API aggiuntivi multiprocesse ha anche backend aggiuntivi. Ma questo è l'essenza.
Mike McKerns,

29

Come altri hanno già detto, è multiprocessingpossibile trasferire oggetti Python solo su processi di lavoro che possono essere decapati. Se non è possibile riorganizzare il codice come descritto da unutbu, è possibile utilizzare le funzionalità dillestese di decapaggio / disimballaggio per il trasferimento di dati (in particolare i dati di codice), come indicato di seguito.

Questa soluzione richiede solo l'installazione dille nessuna altra libreria come pathos:

import os
from multiprocessing import Pool

import dill


def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    return fun(*args)


def apply_async(pool, fun, args):
    payload = dill.dumps((fun, args))
    return pool.apply_async(run_dill_encoded, (payload,))


if __name__ == "__main__":

    pool = Pool(processes=5)

    # asyn execution of lambda
    jobs = []
    for i in range(10):
        job = apply_async(pool, lambda a, b: (a, b, a * b), (i, i + 1))
        jobs.append(job)

    for job in jobs:
        print job.get()
    print

    # async execution of static method

    class O(object):

        @staticmethod
        def calc():
            return os.getpid()

    jobs = []
    for i in range(10):
        job = apply_async(pool, O.calc, ())
        jobs.append(job)

    for job in jobs:
        print job.get()

6
Sono l' autore dille pathos... e anche se hai ragione, non è così più bello, più pulito e più flessibile da usare pathoscome nella mia risposta? O forse sono un po 'di parte ...
Mike McKerns

4
Non ero a conoscenza dello stato di pathosal momento della scrittura e volevo presentare una soluzione molto vicina alla risposta. Ora che ho visto la tua soluzione, sono d'accordo che questa è la strada da percorrere.
Rocksportrocker,

Ho letto la tua soluzione ed è stato come, Doh… I didn't even think of doing it like that. quindi è stato un po 'figo.
Mike McKerns,

4
Grazie per la pubblicazione, ho usato questo approccio per Dilling / argomenti undilling che non potevano essere in salamoia: stackoverflow.com/questions/27883574/...
jazzblue

@rocksportrocker. Sto leggendo questo esempio e non riesco a capire perché esista un forciclo esplicito . Normalmente vedrei la routine parallela prendere un elenco e restituire un elenco senza loop.
user1700890,

20

Ho scoperto che posso anche generare esattamente quell'output di errore su un pezzo di codice perfettamente funzionante tentando di utilizzare il profiler su di esso.

Si noti che questo era su Windows (dove il fork è un po 'meno elegante).

Io stavo correndo:

python -m profile -o output.pstats <script> 

E ho scoperto che la rimozione della profilazione ha rimosso l'errore e il ripristino della profilazione. Mi stava facendo impazzire anche perché sapevo che il codice funzionava. Stavo controllando per vedere se qualcosa aveva aggiornato pool.py ... poi aveva una sensazione di affondamento ed eliminava la profilazione e basta.

Pubblicare qui per gli archivi nel caso in cui qualcuno lo incontri.


3
WOW, grazie per averlo menzionato! Mi ha fatto impazzire per l'ultima ora o giù di lì; Ho provato tutto fino a un esempio molto semplice: nulla sembrava funzionare. Ma ho anche fatto eseguire il profiler al mio batchfile :(
tim

1
Oh, non posso ringraziarti abbastanza. Questo suona così sciocco però, in quanto è così inaspettato. Penso che dovrebbe essere menzionato nei documenti. Tutto quello che avevo era un'istruzione pdb di importazione, e una semplice funzione di livello superiore con solo una passnon era "pickle".
0xc0de,

10

Quando questo problema si presenta con multiprocessinguna soluzione semplice è passare da Poola ThreadPool. Questo può essere fatto senza alcuna modifica del codice oltre all'importazione-

from multiprocessing.pool import ThreadPool as Pool

Ciò funziona perché ThreadPool condivide la memoria con il thread principale, anziché creare un nuovo processo, ciò significa che non è necessario il decapaggio.

L'aspetto negativo di questo metodo è che python non è il miglior linguaggio per la gestione dei thread, ma utilizza qualcosa chiamato Global Interpreter Lock per proteggere i thread, il che può rallentare alcuni casi d'uso qui. Tuttavia, se stai principalmente interagendo con altri sistemi (eseguendo comandi HTTP, parlando con un database, scrivendo su filesystem), probabilmente il tuo codice non è vincolato dalla CPU e non avrà molto successo. In effetti, quando ho scritto benchmark HTTP / HTTPS, ho scoperto che il modello threaded utilizzato qui ha un sovraccarico e ritardi minori, poiché l'overhead derivante dalla creazione di nuovi processi è molto più elevato dell'overhead per la creazione di nuovi thread.

Quindi, se stai elaborando un sacco di cose nello spazio utente di Python, questo potrebbe non essere il metodo migliore.


2
Ma poi stai usando solo una CPU (almeno con le normali versioni di Python che usano GIL ), che tipo di sconfigge lo scopo.
Endre Both,

Dipende davvero da quale sia lo scopo. Il Global Interpreter Lock significa che solo un'istanza alla volta può eseguire codice Python, ma per le azioni che bloccano pesantemente (accesso al file system, download di file di grandi dimensioni o multipli, esecuzione di codice esterno) il GIL finisce per essere un problema. In alcuni casi l'overhead dell'apertura di nuovi processi (anziché dei thread) supera l'overhead GIL.
tedivm,

È vero, grazie. Tuttavia potresti voler includere un avvertimento nella risposta. In questi giorni in cui gli aumenti della potenza di elaborazione si presentano principalmente sotto forma di core CPU più che più potenti, il passaggio dall'esecuzione multicore a quella single core è un effetto collaterale piuttosto significativo.
Endre Both,

Un buon punto: ho aggiornato la risposta con maggiori dettagli. Voglio sottolineare, tuttavia, che il passaggio al multiprocessing threaded non fa funzionare Python solo su un singolo core.
martedì

4

Questa soluzione richiede solo l'installazione di dill e nessuna altra libreria come pathos

def apply_packed_function_for_map((dumped_function, item, args, kwargs),):
    """
    Unpack dumped function as target function and call it with arguments.

    :param (dumped_function, item, args, kwargs):
        a tuple of dumped function and its arguments
    :return:
        result of target function
    """
    target_function = dill.loads(dumped_function)
    res = target_function(item, *args, **kwargs)
    return res


def pack_function_for_map(target_function, items, *args, **kwargs):
    """
    Pack function and arguments to object that can be sent from one
    multiprocessing.Process to another. The main problem is:
        «multiprocessing.Pool.map*» or «apply*»
        cannot use class methods or closures.
    It solves this problem with «dill».
    It works with target function as argument, dumps it («with dill»)
    and returns dumped function with arguments of target function.
    For more performance we dump only target function itself
    and don't dump its arguments.
    How to use (pseudo-code):

        ~>>> import multiprocessing
        ~>>> images = [...]
        ~>>> pool = multiprocessing.Pool(100500)
        ~>>> features = pool.map(
        ~...     *pack_function_for_map(
        ~...         super(Extractor, self).extract_features,
        ~...         images,
        ~...         type='png'
        ~...         **options,
        ~...     )
        ~... )
        ~>>>

    :param target_function:
        function, that you want to execute like  target_function(item, *args, **kwargs).
    :param items:
        list of items for map
    :param args:
        positional arguments for target_function(item, *args, **kwargs)
    :param kwargs:
        named arguments for target_function(item, *args, **kwargs)
    :return: tuple(function_wrapper, dumped_items)
        It returs a tuple with
            * function wrapper, that unpack and call target function;
            * list of packed target function and its' arguments.
    """
    dumped_function = dill.dumps(target_function)
    dumped_items = [(dumped_function, item, args, kwargs) for item in items]
    return apply_packed_function_for_map, dumped_items

Funziona anche con array intorpiditi.


2
Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Questo errore verrà anche se si dispone di una funzione incorporata all'interno dell'oggetto modello che è stata passata al lavoro asincrono.

Quindi assicurati di controllare che gli oggetti del modello passati non abbiano funzioni integrate. (Nel nostro caso stavamo usando la FieldTracker()funzione di django-model-utils all'interno del modello per tracciare un determinato campo). Ecco il link al problema GitHub pertinente.


0

Basandosi sulla soluzione @rocksportrocker, sarebbe logico aneto quando si inviano e si RECVing i risultati.

import dill
import itertools
def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    res = fun(*args)
    res = dill.dumps(res)
    return res

def dill_map_async(pool, fun, args_list,
                   as_tuple=True,
                   **kw):
    if as_tuple:
        args_list = ((x,) for x in args_list)

    it = itertools.izip(
        itertools.cycle([fun]),
        args_list)
    it = itertools.imap(dill.dumps, it)
    return pool.map_async(run_dill_encoded, it, **kw)

if __name__ == '__main__':
    import multiprocessing as mp
    import sys,os
    p = mp.Pool(4)
    res = dill_map_async(p, lambda x:[sys.stdout.write('%s\n'%os.getpid()),x][-1],
                  [lambda x:x+1]*10,)
    res = res.get(timeout=100)
    res = map(dill.loads,res)
    print(res)
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.