2012-02-17 18 views
13

Chciałbym użyć dekoratora funkcji, którą następnie przejdę do puli wieloprocesowej. Jednak kod kończy się niepowodzeniem z opcją "PicklingError: Can not belele: attribute lookup __builtin__. Funkcja nie powiodła się". Nie do końca rozumiem, dlaczego tutaj się nie udało. Czuję, że jest to coś prostego, ale nie mogę go znaleźć. Poniżej znajduje się minimalny "działający" przykład. Pomyślałem, że użycie funkcji functools wystarczy, aby to zadziałało.Python decorator with multiprocessing failed

Jeśli skomentuję dekorację funkcji, działa ona bez problemu. O co chodzi z tym, że nie rozumiem tutaj? Czy jest jakiś sposób, aby to zadziałało?

Edit: Po dodaniu zarówno wpłacone klasy dekoratora i dekorator funkcji, okazuje się, że dekorator funkcja działa zgodnie z oczekiwaniami. Dekorator klasy Callable nadal kończy się niepowodzeniem. Co jest takiego w wersji klasowej, która uniemożliwia jej marynowanie?

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

Ten post zdaje się wskazywać, że trawienie zdobionych przedmiotów jest trudne: http://gael-varoquaux.info/blog/?p = 120 – Daenyth

+0

Tak, znalazłem również tę stronę. Dlatego dodałem opakowanie 'functools'. Ale to nie robi żadnej różnicy. Przyznaję, że tak naprawdę nie rozumiem, co dzieje się pod spodem, aby zobaczyć, dlaczego zawodzi. – agarrett

Odpowiedz

8

Problemem jest to, że zalewa musi mieć jakiś sposób zmontować wszystko, co w marynacie. Zobacz tutaj, aby wyświetlić listę co można marynowane:

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

Po trawieniu my_func następujące składniki muszą być marynowane:

  • instancją my_decorator_class, zwany my_func

    Jest okej. Pickle zapisze nazwę klasy i rozwinie jej zawartość. Po rozpakowaniu używa nazwy do znalezienia klasy, a następnie tworzy instancję i wypełnia zawartość __dict__. Jednak zawartość __dict__ stanowić problem ...

  • Instancja oryginalnego my_func, który jest przechowywany w my_func.target

    To nie jest tak dobry. Jest to funkcja na najwyższym poziomie i zwykle można ją peklować. Pickle zapamiętuje nazwę funkcji. Problem polega jednak na tym, że nazwa "my_func" nie jest już związana z nieodnowioną funkcją, jest związana z dekorowaną funkcją. Oznacza to, że pikle nie będą mogły wyszukać nieodnowionej funkcji do odtworzenia obiektu. Niestety, marynowanie nie ma żadnego sposobu na stwierdzenie, że obiekt, który próbuje się pozbyć, zawsze można znaleźć pod nazwą main .my_func.

Możesz zmienić to w ten sposób i to będzie działać:

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) 

Można zaobserwować, że funkcja działa dekorator, gdy klasa nie. Uważam, że dzieje się tak dlatego, że functools.wraps modyfikuje udekorowaną funkcję tak, że ma nazwę i inne właściwości funkcji, którą otacza. O ile moduł piklarza może powiedzieć, że jest nie do odróżnienia od normalnej funkcji najwyższego poziomu, więc go konserwuje, przechowując jego nazwę. Po spryskaniu nazwa jest przypisana do udekorowanej funkcji, więc wszystko działa.

+0

OK. Więc jeśli chcę, aby te rzeczy się poprawiły, i jeśli chcę użyć klasy wywoływaczy jako mojego dekoratora, to nie będę mógł użyć podejścia dekoracyjnego "@". Będę musiał użyć tego, jak gdybym tworzył prezentację klasy. Czy to jest poprawne? – agarrett

+0

Uważam, że to prawda. Ewentualnie możesz uniknąć marszczenia w ogóle, tworząc trywialną, nie dekorowaną funkcję najwyższego poziomu, która po prostu przesuwa się do udekorowanej funkcji. – Weeble

+0

Bardzo jasne. Dzięki wielkie. – agarrett

Powiązane problemy