2017-07-17 37 views
2

在我的幾個類的,我要同時實現__str____repr__,通常落得這樣的代碼:如何正確地實現兩者__str__和__repr__

class MyClass(object): 
    def __init__(self, a): 
     self.a = a 

    def __str__(self): 
     return 'MyClass({})'.format(self.a) 

    def __repr__(self): 
     return 'MyClass({!r})'.format(self.a) 

這不會是我所期望的:

>>> myobject = MyClass(np.array([1, 2])) 
>>> str(myobject) 
'MyClass([1 2])' 
>>> repr(myobject) 
'MyClass(array([1, 2]))' 

然而代碼違反DRY和參數的數量開始增長保持這個變得麻煩和我經常發現,無論是中__str____repr__來了「不同步」與其他。

有沒有更好的方式同時實現__str____repr__沒有重複?

+1

'str'和'repr'是否真的需要返回兩個不同的字符串? –

+1

你可以說'__str__ = __repr__'(反之亦然)複製實現。 – jasonharper

+0

他們沒有必要返回不同的字符串,但通常更可取。也許這個例子很簡單地表達這一點。 –

回答

1

不需要重複,只是不執行__str__

這樣,對象的行爲將類似於__str__ = __repr__

我想你還應該讀this answer

+0

我很清楚不會重複的可能性,但對於「大」對象,差異,這裏只是'array(...)',可能非常大,並且str和repr都可能有用。 –

+0

然後按照'__repr__'是明確的'__str__'是可讀指南。實現完全取決於你的明確和可讀性的定義。 – nutmeg64

2

對於實現__str____repr__沒有規則也沒有明確的指導原則 - 至少沒有任何地方一貫遵循(即使在stdlib中也沒有)。所以沒有辦法自動獲得「標準行爲」,只是因爲沒有標準行爲。這取決於你,所以如果你爲自己制定指導方針,也許你也可以想出一個實用程序,讓你更容易跟隨它們。

在你的情況,例如,您可以創建一個基類,它提供了__str____repr__實現:

class AutoStRepr(object): 
    _args = [] 
    def __repr__(self): 
     return '{}({})'.format(type(self).__name__, 
      ', '.join(repr(getattr(self, a)) for a in self._args)) 
    def __str__(self): 
     return '{}({})'.format(type(self).__name__, 
      ', '.join(str(getattr(self, a)) for a in self._args)) 

然後,您可以使用在許多不同的類型:

class MyClass(AutoStRepr): 
    _args = ['a'] 
    def __init__(self, a): 
     self.a = a 

class MyOtherClass(AutoStRepr): 
    _args = ['a', 'bc'] 
    def __init__(self, a, b, c): 
     self.a = a 
     self.bc = b * c 
>>> MyClass('foo') 
MyClass('foo') 
>>> MyOtherClass('foo', 2, 5) 
MyOtherClass('foo', 10) 
+0

這不是一個錯誤的答案,但在某種程度上,這違反了原始解決方案中的DRY原則 - 您可能希望將這兩個龐大的.format轉換爲輔助函數。 –

+0

@Rawing那些「大規模加入」?它們如何巨大?這只是一個簡單的生成器表達式的連接。不,這不違反DRY;任何避免重複的解決方案都會增加一些瘋狂的複雜性,但卻沒有實際的好處。 DRY是關於避免一遍又一遍地具有相同的邏輯。這裏是*兩次*,它實際上是一個不同的邏輯,它可能最終分化得更多。 – poke

2

由於您的__str____repr__遵循相同的模式,您可以編寫一個函數爲你創建對象的字符串表示。這將需要一個對象,屬性列表和strrepr作爲參數:

def stringify(obj, attrs, strfunc): 
    values = [] 
    # get each attribute's value and convert it to a string 
    for attr in attrs: 
     value = getattr(obj, attr) 
     values.append(strfunc(value)) 

    # get the class name 
    clsname = type(obj).__name__ 

    # put everything together 
    args = ', '.join(values) 
    return '{}({})'.format(clsname, args) 

print(stringify(MyClass('foo'), ['a'], repr)) 
# output: MyClass('foo') 

我建議把這個函數的一類,你再繼承:

class Printable: 
    def __str__(self): 
     return self.__stringify(str) 

    def __repr__(self): 
     return self.__stringify(repr) 

    def __stringify(self, strfunc): 
     values = [] 
     for attr in self._attributes: 
      value = getattr(self, attr) 
      values.append(strfunc(value)) 

     clsname = type(self).__name__ 
     args = ', '.join(values) 
     return '{}({})'.format(clsname, args) 

class MyClass(Printable): 
    _attributes = ['a'] 

    def __init__(self, a): 
     self.a = a 

而且您甚至可以通過直接從__init__函數的簽名中獲取屬性完全自動完成:

import inspect 

class AutoPrintable: 
    def __str__(self): 
     return self.__stringify(str) 

    def __repr__(self): 
     return self.__stringify(repr) 

    def __stringify(self, strfunc): 
     sig= inspect.signature(self.__init__) 
     values= [] 
     for attr in sig.parameters: 
      value= getattr(self, attr) 
      values.append(strfunc(value)) 

     clsname= type(self).__name__ 
     args= ', '.join(values) 
     return '{}({})'.format(clsname, args) 

class MyClass(AutoPrintable): 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 

print(str(MyClass('foo', 'bar'))) # output: MyClass(foo, bar) 
print(repr(MyClass('foo', 'bar'))) # output: MyClass('foo', 'bar')