2017-07-17 42 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')