2012-08-22 24 views
7

我想爲屬於我的擴展庫的對象實現酸洗支持。有一個啓動時初始化的Service類的全局實例。所有這些對象都是作爲一些Service方法調用的結果而產生的,並且實質上屬於它們。 Service知道如何將它們序列化爲二進制緩衝區以及反序列化緩衝區如何返回到對象中。Python的__reduce __/copy_reg語義和狀態unpickler

似乎Pythons __ reduce__應該服務於我的目的 - 實施酸洗支持。我開始實現一個,並意識到unpickler存在一個問題(第一個元素od預期會由__ reduce__返回)。這個unpickle函數需要一個Service的實例,以便能夠將輸入緩衝區轉換爲Object。這裏有一些僞代碼來說明問題:

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     # do serialization here and return buffer 
     ... 

    def unpickleObject(self,buffer): 
     # do deserialization here and return new Object 
     ... 

class Object(object): 
    ... 
    def __reduce__(self): 
     return self.service().unpickleObject, (self.service().pickleObject(self),) 

請注意元組中的第一個元素。 Python pickler不喜歡它:它說它是實例方法,不能被醃製。顯然,pickler正在嘗試將例程存儲到輸出中,並希望Service實例和函數名一起使用,但這並不是我想要發生的事情。我不希望(並且實際上不能:服務不可選)將服務與所有對象一起存儲。我希望在調用pickle.load之前創建服務實例,並在取出過程中以某種方式使用該實例。

在這裏,我來了copy_reg模塊。它再次出現,因爲它應該解決我的問題。這個模塊允許動態註冊每種類型的pickler和unpickler例程,並且這些應該稍後用於這種類型的對象。所以我添加該登記到服務結構:現在

class Service(object): 
    ... 
    def __init__(self): 
     ... 
     import copy_reg 
     copy_reg(mymodule.Object, self.pickleObject, self.unpickleObject) 

self.unpickleObject是綁定方法以服務爲第一參數和緩衝液作爲第二。 self.pickleObject也是綁定的方法,將服務和對象醃製。 copy_reg要求pickleObject例程應該跟隨reducer語義並返回與之前類似的元組。這裏又出現了問題:我應該返回什麼作爲第一個元組?

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     ... 
     return self.unpickleObject, (self.serialize(obj),) 

在這種形式中,泡菜再次抱怨它不能泡菜的實例方法。我試過無 - 它也不喜歡它。我放了一些虛擬功能。這是有效的 - 意味着序列化過程很好,但在取消打開過程中,它會調用這個虛函數而不是unpickler,我在Service構造函數中註冊了mymodule.Object類型。

所以現在我處於虧損狀態。對不起,很長的解釋:我不知道如何在幾行中提出這個問題。我可以總結我的這樣的問題:

  1. 爲什麼copy_reg語義要求我從pickleObject返回unpickler例程,如果我希望獨立註冊一個?
  2. 是否有任何理由更喜歡copy_reg.constructor接口註冊unpickler例程?
  3. 如何讓泡菜使用我註冊的取代程序而不是一個內置的流?
  4. 作爲pickleObject結果值的元組中的第一個元素應該返回什麼?有沒有「正確」的價值?
  5. 我是否正確地處理了這件事?有不同/更簡單的解決方案嗎?

謝謝你的時間。

Gennadiy

回答

3

首先,在copy_reg模塊是不可能幫助你在這裏多:它主要是一種方法,像功能添加到__reduce__類,沒有這樣的方法,而不是提供任何特殊能力(例如,如果你想從一些本來不支持它的庫中醃製對象)。

__reduce__返回的可調用對象需要在需要取消對象的環境中定位,因此實例方法並不合適。正如Pickle documentation提到:

在在unpickle環境中,該對象必須是一個類,可調用註冊爲 「安全構造」(見下文),或者必須有一個真正的屬性 __safe_for_unpickling__值。

因此,如果你定義的函數(沒有方法)如下:

def _unpickle_service_object(buffer): 
    # Grab the global service object, however that is accomplished 
    service = get_global_service_object() 
    return service.unpickleObject(buffer) 

_unpickle_service_object.__safe_for_unpickling__ = True 

你現在可以在你的__reduce__方法的返回值,使用此_unpickle_service_object功能,使你的對象鏈接到新環境的unpickled時全局Service對象。

+0

您沒有回答上述問題。 –

+0

我最終做了類似的事情。儘管我稱之爲全局服務實例,但這並不完全正確。在生產應用中確實只有一個實例,但沒有說它是全局的。事實上,在我的單元測試中,我一直重複地創建一個新的。 我在包中使用例程def register(svc)和全局模塊級變量服務在寄存器中設置了pickle.py模塊。此例程還使用copy_reg註冊酸洗/取消功能。這個例程從Service構造函數調用。但有幾個問題: –

+0

1.這隻適用於單個服務實例。換句話說,我們不能有兩種不同的服務同時存在,並且都有可供選擇的對象。 2.此全局參考是潛在內存泄漏的來源。如何以及何時需要清理它? –