2012-02-17 16 views
13

Vorrei utilizzare un decoratore su una funzione che successivamente passerò a un pool di multiprocessing. Tuttavia, il codice non funziona con "PicklingError: Can pickle: lookup degli attributi __builtin__ .function failed". Non vedo proprio perché fallisce qui. Sono certo che sia qualcosa di semplice, ma non riesco a trovarlo. Di seguito è riportato un esempio "funzionante" minimo. Pensavo che usare la funzione functools sarebbe stato sufficiente per far funzionare tutto questo.Decoratore Python con multielaborazione fallita

Se commento la decorazione della funzione, funziona senza problemi. Di cosa si tratta multiprocessing che sto fraintendendo qui? C'è un modo per farlo funzionare?

Edit: Dopo l'aggiunta sia di una classe decoratrice callable e una funzione decoratore, si scopre che la funzione decoratore funziona come previsto. Il decoratore di classi chiamabili continua a fallire. Di cosa si tratta nella versione della classe chiamabile che impedisce che venga decapata?

import random 
import multiprocessing 
import functools 

class my_decorator_class(object): 
    def __init__(self, target): 
     self.target = target 
     try: 
      functools.update_wrapper(self, target) 
     except: 
      pass 

    def __call__(self, elements): 
     f = [] 
     for element in elements: 
      f.append(self.target([element])[0]) 
     return f 

def my_decorator_function(target): 
    @functools.wraps(target) 
    def inner(elements): 
     f = [] 
     for element in elements: 
      f.append(target([element])[0]) 
     return f 
    return inner 

@my_decorator_function 
def my_func(elements): 
    f = [] 
    for element in elements: 
     f.append(sum(element)) 
    return f 

if __name__ == '__main__': 
    elements = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)] 
    pool = multiprocessing.Pool(processes=4) 
    results = [pool.apply_async(my_func, ([e],)) for e in elements] 
    pool.close() 
    f = [r.get()[0] for r in results] 
    print(f) 
+0

Questo post sembra indicare che il decapaggio degli oggetti decorati è complicato: http://gael-varoquaux.info/blog/?p = 120 – Daenyth

+0

Sì, ho trovato anche quella pagina. Ecco perché ho aggiunto il wrapper 'functools'. Ma non sembra fare alcuna differenza. Confesso di non capire veramente cosa sta succedendo sotto per vedere perché fallisce. – agarrett

risposta

8

Il problema è che salamoia ha bisogno di avere un modo per rimontare il tutto che si salamoia. Vedi qui per un elenco di ciò che può essere in salamoia:

http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled

Quando decapaggio my_func, i seguenti componenti devono essere in salamoia:

  • Un'istanza di my_decorator_class, chiamato my_func

    Questo va bene. Pickle memorizzerà il nome della classe e sottaceto il suo contenuto di __dict__. Quando si deseleziona, utilizza il nome per trovare la classe, quindi crea un'istanza e riempie i contenuti di __dict__. Tuttavia, i contenuti __dict__ presentano un problema ...

  • L'istanza del my_func originale che è memorizzato in my_func.target

    Questo non è così buono. È una funzione di livello superiore e normalmente questi possono essere decapati. Pickle memorizzerà il nome della funzione. Il problema, tuttavia, è che il nome "my_func" non è più legato alla funzione non decorata, è legato alla funzione decorata. Ciò significa che pickle non sarà in grado di cercare la funzione non decorata per ricreare l'oggetto. Purtroppo, pickle non ha modo di sapere che l'oggetto che sta cercando di sottaceto può sempre essere trovato sotto il nome principale .my_func.

È possibile modificare in questo modo e funzionerà:

import random 
import multiprocessing 
import functools 

class my_decorator(object): 
    def __init__(self, target): 
     self.target = target 
     try: 
      functools.update_wrapper(self, target) 
     except: 
      pass 

    def __call__(self, candidates, args): 
     f = [] 
     for candidate in candidates: 
      f.append(self.target([candidate], args)[0]) 
     return f 

def old_my_func(candidates, args): 
    f = [] 
    for c in candidates: 
     f.append(sum(c)) 
    return f 

my_func = my_decorator(old_my_func) 

if __name__ == '__main__': 
    candidates = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)] 
    pool = multiprocessing.Pool(processes=4) 
    results = [pool.apply_async(my_func, ([c], {})) for c in candidates] 
    pool.close() 
    f = [r.get()[0] for r in results] 
    print(f) 

Avete osservato che la funzione decoratore funziona quando la classe non lo fa. Credo che questo sia dovuto al fatto che lo functools.wraps modifica la funzione decorata in modo che abbia il nome e altre proprietà della funzione che essa avvolge. Per quanto riguarda il modulo pickle, è indistinguibile da una normale funzione di primo livello, quindi lo mette sottosopra memorizzando il suo nome. Dopo aver deselezionato, il nome è associato alla funzione decorata, quindi tutto funziona.

+0

OK. Quindi se voglio che queste cose si mettano a picco, e se voglio usare una classe chiamabile come decoratore, allora non sarò in grado di usare l'approccio di decorazione '@'. Dovrò usarlo come se stessimo istanziando la classe. È corretto? – agarrett

+0

Credo sia corretto. In alternativa, è possibile evitare di picchettarlo creando una funzione di livello superiore banale non decorata che viene semplicemente inoltrata alla funzione decorata. – Weeble

+0

Molto chiaro. Grazie mille. – agarrett

Problemi correlati