2012-02-27 120 views
2

我有一個類,其實例需要按照用戶的指示格式化輸出。有一個默認格式,可以覆蓋。我這樣實現它:酸洗類方法

class A: 
    def __init__(self, params): 
    # ... 
    # by default printing all float values as percentages with 2 decimals 
    self.format_functions = {float: lambda x : '{:.2%}'.format(x)} 
    def __str__(self): 
    # uses self.format_functions to format output 
    # ... 

a = A(params) 
print(a) # uses default output formatting 

# overriding default output formatting 
# float printed as percentages 3 decimal digits; bool printed as Y/N 
a.format_functions = {float : lambda x: '{:.3%}'.format(x), 
         bool : lambda x: 'Y' if x else 'N'} 
print(a) 

可以嗎?讓我知道是否有更好的方法來設計這個。我不得不挑剔這個類的實例。但是隻有模塊頂層定義的函數才能被醃製; lambda功能是不可取消的,所以我的format_functions實例屬性打破了酸洗。

我試圖重寫這個使用一個類的方法,而不是lambda函數,但仍沒有運氣出於同樣的原因:

class A: 
    @classmethod 
    def default_float_format(cls, x): 
    return '{:.2%}'.format(x) 
    def __init__(self, params): 
    # ... 
    # by default printing all float values as percentages with 2 decimals 
    self.format_functions = {float: self.default_float_format} 
    def __str__(self): 
    # uses self.format_functions to format output 
    # ... 

a = A(params) 
pickle.dump(a) # Can't pickle <class 'method'>: attribute lookup builtins.method failed 

注意,這裏酸洗即使我不覆蓋不起作用默認值;只是我分配self.format_functions = {float : self.default_float_format}的事實打破了它。

怎麼辦?我寧願不污染命名空間並通過在模塊級定義default_float_format來打破封裝。

順便說一句,爲什麼在這個世界上pickle創造了這個限制?這對於最終用戶來說當然感覺像是一種無端的和實質性的痛苦。

回答

3

對於酸洗類實例或函數(以及方法),Python的pickle依賴於它們的名稱可用作全局變量 - 對字典中方法的引用指向全局名稱空間中不可用的名稱 - 這更好地說,「模塊命名空間」 -

你可以通過自定義你的類的酸洗,通過創建「__setstate__」和「__getstate__」方法 - 但我認爲你會更好,因爲格式化函數不依賴於對象或類本身的任何信息(即使某些格式化函數可以,也可以將它作爲參數傳遞),並在類作用域之外定義一個函數。

這並不工作(Python的3.2):

def default_float_format(x): 
    return '{:.2%}'.format(x) 

class A: 

    def __init__(self, params): 
    # ... 
    # by default printing all float values as percentages with 2 decimals 
    self.format_functions = {float: default_float_format} 
    def __str__(self): 
    # uses self.format_functions to format output 
    pass 

a = A(1) 
pickle.dumps(a) 
+1

我不介意,但如果另一個類需要不同的默認值呢?然後,我將在模塊級別擁有'default_float_format_for_class_A','default_float_format_for_class_B'等。它們不是更好的類方法(或靜態方法)嗎?另外,如果'pickle'可以與類方法一起工作,爲什麼不在標準的'pickle'模塊中完成呢?這有什麼缺點嗎? – max 2012-02-27 19:27:57

2

如果使用dill模塊,無論是你的兩種方法都將只是 「工作」 爲是dill可以泡菜lambda以及類和類方法的實例。

沒有必要污染命名空間和破解封裝,正如你所說你不想這樣做......但其他答案確實是

dill基本上是十年左右的時間找到正確的copy_reg函數,它記錄瞭如何序列化標準python中的大部分對象。沒有特別的或棘手的,它只是需要時間。那麼爲什麼不pickle爲我們做這個?爲什麼pickle有這個限制?

好吧,如果你看一下pickle文檔,答案是有: https://docs.python.org/2/library/pickle.html#what-can-be-pickled-and-unpickled

基本上是:函數和類通過引用醃製。

這意味着pickle不會在__main__定義對象的工作,而且也沒有很多動態修改對象。 dill作爲模塊註冊__main__,所以它有一個有效的命名空間。 dill也給你選擇不參考引用,所以你可以序列化動態修改對象...和類實例,類方法(綁定和未綁定),等等。

+0

觀察:你錯過了一個蒔蘿鏈接 – 2016-10-05 18:18:39

+1

https://github.com/uqfoundation/dill – 2016-10-07 07:25:29