简体   繁体   English

如何正确实现 __str__ 和 __repr__

[英]How to properly implement both __str__ and __repr__

In several of my classes, I want to implement both __str__ and __repr__ and usually end up with code like this:在我的几个类中,我想同时实现__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)

Which does what I'd expect:这符合我的期望:

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

However the code violates DRY and as the number of arguments starts to grow maintaining this becomes cumbersome and I've often found that either of __str__ or __repr__ has come "out of sync" with the other.然而,代码违反了 DRY 并且随着参数数量开始增长,维护变得很麻烦,我经常发现__str____repr__中的任何一个都与另一个“不同步”。

Is there a better way to simultaneously implement both __str__ and __repr__ without duplication?有没有更好的方法可以同时实现__str____repr__而不会重复?

Since your __str__ and __repr__ follow the same pattern, you could write a function to create the object's string representation for you.由于您的__str____repr__遵循相同的模式,您可以编写一个函数来为您创建对象的字符串表示。 It would take an object, a list of attributes and str or repr as arguments:它需要一个对象、一个属性列表和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')

I would recommend putting this function in a class which you then inherit from:我建议将此函数放在一个类中,然后从该类中继承:

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

And you can even get it done completely automatically by grabbing the attributes directly from the __init__ function's signature:您甚至可以通过直接从__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')

There are no rules nor clear guidelines for implementing __str__ and __repr__ —at least none that are consistently followed anywhere (not even in the stdlib).实现__str____repr__没有规则也没有明确的指导方针——至少没有一个在任何地方都始终如一地遵循(甚至在标准库中也没有)。 So there wouldn't be a way to get the “standard behavior” automatically, simply because there isn't a standard behavior.所以没有办法自动获得“标准行为”,仅仅因为没有标准行为。 It's up to you, so if you set up guidelines for yourself, maybe you can also come up with a utility to make it easier for you to follow them.这取决于您,因此,如果您为自己设置了指南,也许您还可以想出一个实用程序来让您更轻松地遵循它们。

In your case, you could for example create a base class which provides the __str__ and __repr__ implementations:在您的情况下,您可以例如创建一个提供__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))

You could then use that on a number of different types:然后,您可以在多种不同类型上使用它:

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)

No need for duplication, just don't implement __str__ .不需要重复,只是不要实现__str__

That way, the object will behave like __str__ = __repr__ .这样,对象的行为就像__str__ = __repr__

I think you should also read this answer .我认为您也应该阅读此答案

Neither the official Python documentation nor the Index of Python Enhancement Proposal seem to specify clear guidelines on how to override these methods, except for the 3.3 Special method names which, among other things, says of __repr__() :官方 Python 文档和Python 增强提案索引似乎都没有明确说明如何覆盖这些方法,除了3.3 特殊方法名称,其中提到__repr__()

If at all possible, this should look like a valid Python expression that could be used to recreate an object with the same value [...] This is typically used for debugging, so it is important that the representation is information-rich and unambiguous.如果可能的话,这应该看起来像一个有效的 Python 表达式,可用于重新创建具有相同值的对象 [...] 这通常用于调试,因此表示信息丰富且明确是很重要的.

I like to take inspiration from how __repr__() is implemented in some of the standard library modules, take eg socket.socket :我喜欢从一些标准库模块中如何__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)>

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

While __repr__() is preferred for debug/testing purposes, the scope of __str__() is much more informal and I'd deduce that even looser rules apply.虽然__repr__()首选用于调试/测试目的,但__str__()的范围更为非正式,我推断适用更宽松的规则。 Note that if __repr__() is overridden but __str__() is not, __repr__() calls __str__() .请注意,如果__repr__()被覆盖但__str__()未被覆盖,则__repr__()调用__str__()

Here again, if I have to pick some rules I prefer to have __str__() resemble __repr__() , but modifying:再说一次,如果我必须选择一些规则,我更喜欢让__str__()类似于__repr__() ,但要修改:

  • The number of items displayed.显示的项目数。 I don't need to be verbose as __repr__ mandates.我不需要像__repr__要求那样冗长。
  • The type of values displayed.显示的值的类型。 I include the most "important" ones, and even values that do not reflect the arguments initially passed to __init__() .我包括最“重要”的那些,甚至包括不反映最初传递给__init__()的参数的值。

Another few examples come from a PDF library I've been working on.另外几个例子来自我一直在研究的一个 PDF 库。 There are two PdfFileReader and PdfFileWriter classes, and their __repr__() and __str__() methods have the following output:有两个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>

See also 2. Built-in Functions for repr() :另请参见 2. repr()的内置函数

[...] For many types, this function makes an attempt to return a string that would yield an object with the same value when passed to eval() , otherwise the representation is a string enclosed in angle brackets that contains the name of the type of the object together with additional information often including the name and address of the object. [...] 对于许多类型,此函数尝试返回一个字符串,该字符串在传递给 eval() 时会产生具有相同值的对象,否则表示是一个包含在尖括号中的字符串,其中包含对象的类型以及通常包括对象的名称和地址的附加信息。 [...] [...]

How about this example:这个例子怎么样:

#!/usr/bin/env python3

class Quaternion:
    _x: float = 0.0
    _y: float = 0.0
    _z: float = 0.0
    _w: float = 1.0

    @property
    def x(self) -> float:
        return self._x

    @property
    def y(self) -> float:
        return self._y

    @property
    def z(self) -> float:
        return self._z

    @property
    def w(self) -> float:
        return self._w

    def __init__(self, x: float, y: float, z: float, w: float) -> None:
        self._x = float(x)
        self._y = float(y)
        self._z = float(z)
        self._w = float(w)

    def __str__(self) -> str:
        return ", ".join(
            (
                str(self._x),
                str(self._y),
                str(self._z),
                str(self._w)
            )
        )

    def __repr__(self) -> str:
        cls = self.__class__
        module = cls.__module__
        return f"{module + '.' if module != '__main__' else ''}{cls.__qualname__}({str(self)})"

Pros优点

  • str() returns the pure values: 0.0, 0.0, 0.0, 1.0 str()返回纯值: 0.0, 0.0, 0.0, 1.0
  • repr() returns something like: Quaternion(0.0, 0.0, 0.0, 1.0) or mymodule.Quaternion(0.0, 0.0, 0.0, 1.0) (if it's in an imported submodule) . repr()返回类似: Quaternion(0.0, 0.0, 0.0, 1.0)mymodule.Quaternion(0.0, 0.0, 0.0, 1.0) (如果它在导入的子模块中) So you are able to reproduce it easily by eval(repr())因此,您可以通过eval(repr())轻松重现它

Cons缺点

Don't know any, yet.还不知道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM