In several of my classes, I want to implement both __str__
and __repr__
and usually end up with code like this:
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.
Is there a better way to simultaneously implement both __str__
and __repr__
without duplication?
Since your __str__
and __repr__
follow the same pattern, you could write a function to create the object's string representation for you. It would take an object, a list of attributes and str
or repr
as arguments:
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:
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). 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:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With