2012-11-18 48 views
0

它的錯誤我是一類的默認__repr__()是如此無信息:這個抽象基類帶有「更好的」__repr __()危險嗎?

>>> class Opaque(object): pass 
... 
>>> Opaque() 
<__main__.Opaque object at 0x7f3ac50eba90> 

...所以我一直在思考如何改進它。考慮一點後,我想出了這個抽象基類,它充分利用了pickle協議的__getnewargs__()方法:

from abc import abstractmethod 

class Repro(object): 

    """Abstract base class for objects with informative ``repr()`` behaviour.""" 

    @abstractmethod 
    def __getnewargs__(self): 
     raise NotImplementedError 

    def __repr__(self): 
     signature = ", ".join(repr(arg) for arg in self.__getnewargs__()) 
     return "%s(%s)" % (self.__class__.__name__, signature) 

下面是其使用的一個簡單的例子:

class Transparent(Repro): 

    """An example of a ``Repro`` subclass.""" 

    def __init__(self, *args): 
     self.args = args 

    def __getnewargs__(self): 
     return self.args 

...所得repr()行爲:

>>> Transparent("an absurd signature", [1, 2, 3], str) 
Transparent('an absurd signature', [1, 2, 3], <type 'str'>) 
>>> 

現在,我可以看到一個原因,默認情況下Python不會立即執行 - requiri每個班級定義一個__getnewargs__()方法將比預期(但不要求)它定義一個__repr__()方法更負擔。

我想知道的是:它有多危險和/或脆弱?副手,我想不出任何可能會出現嚴重錯誤的情況,除非如果一個Repro實例包含它自己,你會得到無限遞歸...但是這是可以解決的,代價是使代碼高於醜陋。

我還錯過了什麼?

+0

'repr'大約是* *的方式之前存在着任何東西,如'__getnewargs__'。你也在濫用一種方法。它是由一個庫模塊(而不是python語言核心本身)引入的,可以完成一個完全不同的事情。還有關鍵字參數呢?在python3中,可以只有構造函數的關鍵字參數。 此外,我沒有看到任何真正的額外好處。對象可以有一個簡單的字符串表示*應該*來實現'__repr__'和/或'__str__'。使用你的方法,你仍然需要重新實現它,或者你必須重新實現'__getnewargs__'。 – Bakuriu

+0

沒有真正的理由爲此使用'__getnewargs__',是嗎?你可以定義一個約定,一個對象將一些識別信息存儲在一個任意屬性中(比如'_reprInfo'),然後'__repr__'輸出'self._reprInfo'。 – BrenBarn

+0

@Bakuriu我怎麼濫用它呢?一個正確實現的'Repro'的子類將有一個正確的每個pickle'__getnewargs __()',這甚至可以被認爲是一個優點。我意識到這不適用於具有關鍵字參數構造函數的類,但是我不會將它用於這些......實際上,如果可以,我會盡量避免構造函數中的關鍵字參數。 –

回答

2

這個想法的一個問題是,可能有某些對象的狀態不完全依賴於給它的構造函數的參數。對於一個微不足道的情況下,可以考慮一類具有隨機狀態:

import random 

def A(object): 
    def __init__(self): 
     self.state = random.random() 

有沒有辦法讓這個類來正確地實現__getnewargs__,所以你的__repr__植入也是不可能的。可能是像上面這樣的課程設計不好。但pickle可以處理它沒有任何問題(我假設使用從object繼承的__reduce__方法,但我的pickle-fu是不足以確定地說)。

這就是爲什麼很高興__repr__可以編碼做任何你想要的。如果你想讓內部狀態可見,你可以讓你的課程__repr__這樣做。如果對象應該是不透明的,你也可以這樣做。對於上面的類,我可能會實現__repr__這樣的:

def __repr__(self): 
    return "<A object with state=%f>" % self.state 
+0

這是一個很好的反例,我還沒有考慮過 - 謝謝!在接受查看是否有其他人出現之前,我會等待......你永遠不會知道...... –

+0

對我來說,就像這個類可能暗示'__getnewargs',如果它也實現了接受狀態參數的'__new__'方法。 – martineau

4

如果你是這種事情,爲什麼不使用裝飾器從__init__自動拾取參數?然後,您不需要用戶手動處理它們,並且可以透明地處理具有多個參數的常規方法簽名。下面是一個簡單的版本,我想出了:

def deco(f): 
    def newFunc(self, *args, **kwargs): 
     self._args = args 
     self._kwargs = kwargs 
     f(self, *args, **kwargs) 
    return newFunc 
class AutoRepr(object): 
    def __repr__(self): 
     args = ', '.join(repr(arg) for arg in self._args) 
     kwargs = ', '.join('{0}={1}'.format(k, repr(v)) for k, v in self._kwargs.iteritems()) 
     allArgs = ', '.join([args, kwargs]).strip(', ') 
     return '{0}({1})'.format(self.__class__.__name__, allArgs) 

現在可以正常定義AutoRepr的子類,與正常__init__簽名:

class Thingy(AutoRepr): 
    @deco 
    def __init__(self, foo, bar=88): 
     self.foo = foo 
     self.bar = bar 

而且__repr__自動工作:

>>> Thingy(1, 2) 
Thingy(1, 2) 
>>> Thingy(10) 
Thingy(10) 
>>> Thingy(1, bar=2) 
Thingy(1, bar=2) 
>>> Thingy(bar=1, foo=2) 
Thingy(foo=2, bar=1) 
>>> Thingy([1, 2, 3], "Some junk") 
Thingy([1, 2, 3], 'Some junk') 

@deco放在您的__init__上要比編寫整個__getnewargs__容易得多。如果你甚至不想這樣做,你可以編寫一個元類,以這種方式自動裝飾__init__方法。

+0

* *相當優雅,是的......但這意味着每當您修改對象時,您都必須更新'_args'和'_kwargs'。對我來說,使用'__getnewargs __()'的好處是,爲了糾正'pickle',它必須對'repr'正確,反之亦然,我認爲它可能不那麼脆弱(或者至少更可能早日和顯眼地打破)。 –

+0

@ZeroPiraeus:一個元類可以檢查是否有一個'__getnewargs__'被定義,並使用它來生成'__repr __()',否則就假定裝飾器已經被用在'__init __()'上。 – martineau

+0

@martineau嗯......結合上面的BrenBarn的最終建議,我們會有一個元類,它可以根據一個特殊方法的存在或其他方式來創建另一個特殊方法。這是很多魔術...... –