2012-02-17 19 views
13

我想在隨後傳遞給多處理池的函數上使用裝飾器。但是,代碼失敗,出現「PicklingError:Can not pickle:attribute lookup __builtin__ .function failed」。我不太明白爲什麼它在這裏失敗。我確信這很簡單,但我找不到它。以下是一個最小的「工作」示例。我認爲使用functools函數足以讓這個工作。具有多處理失敗的Python裝飾器

如果我註釋掉函數裝飾,它的工作沒有問題。 multiprocessing我在這裏誤解了什麼?有什麼辦法可以做到這一點?

編輯:同時添加一個可調用的類裝飾功能裝飾後,事實證明,功能裝飾預期工作。可調用類裝飾器繼續失敗。可調用類的版本是什麼使它不被醃製?

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

這篇文章似乎表明,酸洗裝飾對象是棘手的:http://gael-varoquaux.info/blog/?p = 120 – Daenyth 2012-02-17 23:03:55

+0

是的,我也找到了那個頁面。這就是爲什麼我添加了functools包裝器的原因。但它似乎沒有任何區別。我承認我並不真正瞭解下面發生的事情,看看它爲什麼會失敗。 – agarrett 2012-02-17 23:07:13

回答

8

問題是,醃汁需要有一些方法來重新組合所有你醃製的東西。在這裏看到的是什麼,可以醃製的列表:

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

當酸洗my_func,並將下列組件需要醃製:

  • my_decorator_class的實例,稱爲my_func,並將

    這可以。 Pickle將存儲班級的名字並醃製其__dict__內容。取消打開時,它使用名稱查找該類,然後創建一個實例並填充__dict__內容。然而,__dict__內容提出一個問題...

  • 一個儲存在my_func.target

    這也不是那麼好原my_func,並將實例。這是頂層的功能,通常這些都可以被醃製。醃菜將存儲該功能的名稱。但問題是,名稱「my_func」不再綁定到未裝飾函數,而是綁定到裝飾函數。這意味着pickle將無法查找未修飾的函數來重新創建對象。可悲的是,泡菜沒有辦法知道它試圖醃製的物體總是可以在名稱main.my_func中找到。

你可以改變它這樣,它會工作:

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) 

你已經注意到了裝飾功能上課的時候不工作。我相信這是因爲functools.wraps修改了裝飾函數,以便它具有它所包裝函數的名稱和其他屬性。就pickle模塊而言,它與普通的頂級函數無法區分,所以它通過存儲它的名字來醃製它。取消打開後,名稱將被綁定到裝飾函數,以便一切正常。

+0

好的。所以,如果我想要這些東西來醃製,並且如果我想使用可調用的類作爲我的裝飾器,那麼我將無法使用'@'裝飾方法。我將不得不使用它,好像我正在實例化這個類。那是對的嗎? – agarrett 2012-02-17 23:30:45

+0

我相信這是正確的。另外,你可以通過創建一個簡單的非裝飾頂層函數來轉換裝飾函數來避免酸洗它。 – Weeble 2012-02-17 23:39:12

+0

非常清楚。非常感謝。 – agarrett 2012-02-17 23:40:08