Why does the below code print error msg
instead of ABC\nerror msg
?
class CustomException(Exception):
"""ABC"""
def __init__(self, *args):
super().__init__(*args)
self.__str__ = self._wrapper(self.__str__)
def _wrapper(self, f):
def _inner(*args, **kwargs):
return self.__doc__ '\n' f(*args, **kwargs)
return _inner
print(CustomException('error msg'))
CodePudding user response:
Forget the Exception class for now. Consider this:
class A:
def __str__(self):
return 'A'
obj = A()
print(obj)
obj.__str__ = lambda x: 'B'
print(obj)
A.__str__ = lambda x: 'B'
print(obj)
output:
A
A # Not B !
B
Dunder methods' lookup is different in Python. From docs:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.
What you want to achieve is:
class CustomException(Exception):
"""ABC"""
def __init__(self, *args):
super().__init__(*args)
def __str__(self):
return self.__doc__ '\n' super().__str__()
print(CustomException('error msg'))
output:
ABC
error msg
CodePudding user response:
Operations backed by special methods usually explicitly look up the special method as a proper method not just as a callable attribute. Concretely, instead of self.__str__
the interpreter roughly looks at type(self).__str__.__get__(self, type(self))
– i.e. a descriptor __str__
on the class to be bound with the instance. To override a special method, it is thus necessary to override the class' descriptor instead of the instance' attribute.
This can be done by a) declaring the special method as a slot, which handles the type(self).__str__
part, and b) assigning a function, which handles the __get__(self, type(self))
part.
class CustomException(Exception):
"""ABC"""
__slots__ = ("__str__",) # <<< magic
def __init__(self, *args):
super().__init__(*args)
# vvv self.__str__ is the class' slot
self.__str__ = self._wrapper(super().__str__)
# AAA real __str__ lives on the super class
def _wrapper(self, f):
def _inner(*args, **kwargs):
return self.__doc__ '\n' f(*args, **kwargs)
return _inner
print(CustomException('error msg'))
Note that since every instance behaves the same in this case, it is advisable to just define a new __str__
method in practice.
CodePudding user response:
As per @khelwood's reply in the comments this code works as intended:
class CustomException(Exception):
"""ABC"""
def __init__(self, *args):
super().__init__(*args)
self.__class__.__str__ = self._wrapper(self.__str__)
def _wrapper(self, f):
def _inner(*args, **kwargs):
return self.__doc__ '\n' f()
return _inner
but I ended up with this equivalent (@MisterMiyagi):
class CustomException(Exception):
"""ABC"""
def __str__(self):
return self.__doc__ '\n' super().__str__()