【问题标题】:How to properly implement both __str__ and __repr__如何正确实现 __str__ 和 __repr__
【发布时间】:2017-07-17 20:48:33
【问题描述】:

在我的几个课程中,我想同时实现__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__ 而不会重复?

【问题讨论】:

  • strrepr真的有必要返回两个不同的字符串吗?
  • 你可以直接说__str__ = __repr__(反之亦然)来复制实现。
  • 它们没有必要返回不同的字符串,但通常更可取。也许这个例子很容易传达。
  • 你可能想看看attrs

标签: python


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    对于实现__str____repr__,没有任何规则或明确的指导方针——至少没有任何地方始终如一地遵循(甚至在标准库中也没有)。所以没有办法自动获得“标准行为”,仅仅因为没有标准行为。这取决于您,因此,如果您为自己设置指南,也许您还可以想出一个实用程序来让您更轻松地遵循它们。

    在您的情况下,例如,您可以创建一个提供 __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)
    

    【讨论】:

    • 不错的答案,但在某种程度上,这与原始解决方案一样违反了 DRY 原则 - 您可能希望将这两个巨大的 .formats 移动到辅助函数中。
    • @Rawing 那些“大规模连接”?它们有多大?它只是一个简单的生成器表达式的连接。不,这不违反 DRY;任何避免重复的解决方案只会增加疯狂的复杂性,而没有实际的好处。 DRY 就是要避免一遍又一遍地使用相同的逻辑。这是两次,实际上是不同的逻辑,最终可能会更加分歧。
    【解决方案3】:

    不需要重复,只是不要实现__str__

    这样,对象的行为就像__str__ = __repr__

    我认为你也应该阅读this answer

    【讨论】:

    • 我很清楚不重复的可能性,但是对于“大”对象,差异,这里只是 array(...),可能非常大,同时拥有 str 和 repr 可能很有用。
    • 然后遵循__repr__ 是明确的__str__ 是可读的准则。实现完全取决于您对明确和可读的定义。
    【解决方案4】:

    官方 Python 文档和 Index of Python Enhancement Proposal 似乎都没有明确说明如何覆盖这些方法,除了 3.3 Special method names,其中提到了 __repr__()

    如果可能的话,这应该看起来像一个有效的 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)>
    

    所以这里的模式是&lt;self.__class__.__module__ + "." + self.__class__.__name__ attribute1=value1, ..., attributeN=valueN&gt;

    虽然__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>
    

    对于repr(),另见2. Built-in Functions

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-25
      • 2018-11-23
      • 2013-01-28
      • 2020-02-07
      • 1970-01-01
      • 2011-04-11
      相关资源
      最近更新 更多