2012-02-17 22 views
13

Me gustaría utilizar un decorador en una función que posteriormente pasaré a un grupo de multiprocesamiento. Sin embargo, el código falla con "PicklingError: Can not pickle: búsqueda de atributo __builtin__ .function failed". No entiendo por qué falla aquí. Estoy seguro de que es algo simple, pero no puedo encontrarlo. A continuación se muestra un ejemplo mínimo de "trabajo". Pensé que usar la función functools sería suficiente para que esto funcione.El decorador de Python con multiprocesamiento falla

Si comento la decoración de la función, funciona sin problemas. ¿De qué se trata multiprocessing que estoy malentendiendo aquí? ¿Hay alguna manera de hacer funcionar esto?

Editar: Después de añadir tanto un decorador de clase exigible y un decorador función, resulta que el decorador función funciona como se espera. El decorador de clase invocable sigue fallando. ¿De qué se trata la versión de clase invocable que evita que se escatime?

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

Esta publicación parece indicar que la decapación de objetos decorados es complicada: http://gael-varoquaux.info/blog/?p = 120 – Daenyth

+0

Sí, encontré esa página, también. Es por eso que agregué el contenedor 'functools'. Pero no parece hacer ninguna diferencia. Confieso que realmente no entiendo lo que está sucediendo debajo para ver por qué falla. – agarrett

Respuesta

8

El problema es que pickle debe tener alguna manera de volver a montar todo lo que salga en vinagre. Ver aquí para una lista de lo que puede ser en escabeche:

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

Cuando decapado mi_func, los siguientes componentes deben ser en escabeche:

  • Una instancia de my_decorator_class, llamado mi_func

    Esto esta bien. Pickle almacenará el nombre de la clase y saldrá en su contenido __dict__. Cuando se deshace, usa el nombre para encontrar la clase, luego crea una instancia y completa el contenido de __dict__. Sin embargo, los contenidos __dict__ presentan un problema ...

  • La instancia de la mi_func original que está almacenado en my_func.target

    Esto no es tan buena. Es una función en el nivel superior, y normalmente se puede escabechar. Pickle almacenará el nombre de la función. El problema, sin embargo, es que el nombre "my_func" ya no está vinculado a la función no decorada, está vinculado a la función decorada. Esto significa que pickle no podrá buscar la función no decorada para recrear el objeto. Lamentablemente, pickle no tiene forma de saber que el objeto que trata de salmuera siempre se puede encontrar bajo el nombre principal .my_func.

Puede cambiarlo como éste se ponga a funcionar:

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) 

Usted ha observado que la función decoradora funciona cuando la clase no lo hace. Creo que esto se debe a que functools.wraps modifica la función decorada para que tenga el nombre y otras propiedades de la función que envuelve. En lo que respecta al módulo de pickle, es indistinguible de una función de nivel superior normal, por lo que lo conserva guardando su nombre. Al desatornillar, el nombre está ligado a la función decorada para que todo salga bien.

+0

OK. Entonces, si quiero que estas cosas salgan a relucir, y si quiero usar una clase invocable como mi decorador, entonces no podré usar el enfoque de decoración '@'. Tendré que usarlo como si estuviera creando una instancia de la clase. ¿Es eso correcto? – agarrett

+0

Creo que es correcto. Alternativamente, puede evitar el decapado creando una función trivial no decorada de nivel superior que se reenvía a la función decorada. – Weeble

+0

Muy claro. Muchas gracias. – agarrett

Cuestiones relacionadas