如何正确实现__str__和__repr __

时间:2017-07-17 20:48:34

标签: python

在我的几个课程中,我想要同时实现__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__中的任何一个与“{0}}”不同步其他

是否有更好的方法可以同时实现__str____repr__而无需重复?

4 个答案:

答案 0 :(得分:3)

由于您的__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')

答案 1 :(得分: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)

答案 2 :(得分:1)

无需重复,只是不要实施VLOOKUP

这样,对象的行为就像__str__

我认为您还应该阅读this answer

答案 3 :(得分:1)

除了Index of Python Enhancement Proposal表示__repr__()的{​​{3}}以外,官方Python文档和3.3 Special method names似乎都没有明确说明如何覆盖这些方法。 / p>

  

如果可能的话,这看起来应该像一个有效的Python表达式   可以用来重新创建具有相同值的对象[...]   这通常用于调试,因此重要的是   表示形式是信息丰富且明确的。

我想从__repr__()在某些标准库模块中的实现方式中汲取灵感,例如socket.socket

$ python3
>>> from socket import socket
>>> socket()
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>

所以这里的模式是<self.__class__.__module__ + "." + self.__class__.__name__ attribute1=value1, ..., attributeN=valueN>

虽然__repr__()是调试/测试目的的首选,但__str__()的范围却更为非正式,我推断甚至适用更宽松的规则。请注意,如果__repr__()被覆盖而__str__()没有被覆盖,则__repr__()会调用__str__()

再次,如果我必须选择一些规则,我更希望__str__()类似于__repr__(),但是要进行修改:

  • 显示的项目数。我不需要像__repr__那样冗长。
  • 显示的值的类型。我包括了最“重要”的参数,甚至包括那些不能反映最初传递给__init__()的参数的值。

另外一些例子来自我一直在研究的PDF库。有两个PdfFileReaderPdfFileWriter类,它们的__repr__()__str__()方法具有以下输出:

r = PdfFileReader("samplecode/pdfsamples/jpeg.pdf")
w = PdfFileWriter()

print(r)
print(str(r))
print(repr(r))

print(w)
print(str(w))
print(repr(w))

$ python3 repr.py
<pypdf.pdf.PdfFileReader _filepath=samplecode/pdfsamples/jpeg.pdf, stream=<_io.BytesIO object at 0x7eff60f07e60>, strict=True, debug=False>
<pypdf.pdf.PdfFileReader _filepath=samplecode/pdfsamples/jpeg.pdf, stream=<_io.BytesIO object at 0x7eff60f07e60>, strict=True, debug=False>
<pypdf.pdf.PdfFileReader _filepath=samplecode/pdfsamples/jpeg.pdf, stream=<_io.BytesIO object at 0x7eff60f07e60>, strict=True, debug=False>
<pypdf.pdf.PdfFileWriter _header=%PDF-1.3, debug=False>
<pypdf.pdf.PdfFileWriter _header=%PDF-1.3, debug=False>
<pypdf.pdf.PdfFileWriter _header=%PDF-1.3, debug=False>

另请参见2. Built-in Functions中的repr()

  

[...]对于许多类型,此函数都会尝试返回一个字符串,该字符串   当传递给eval()时,将产生具有相同值的对象,   否则,表示形式是用尖括号括起来的字符串   包含对象类型名称以及   附加信息通常包括名称和地址   宾语。 [...]