在我的几个课程中,我想要同时实现__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__
而无需重复?
答案 0 :(得分:3)
由于您的__str__
和__repr__
遵循相同的模式,您可以编写一个函数来为您创建对象的字符串表示形式。它需要一个对象,一个属性列表以及str
或repr
作为参数:
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)
答案 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库。有两个PdfFileReader
和PdfFileWriter
类,它们的__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()时,将产生具有相同值的对象, 否则,表示形式是用尖括号括起来的字符串 包含对象类型名称以及 附加信息通常包括名称和地址 宾语。 [...]