Search code examples
pythonpython-3.xexceptiondecoratorwrapper

__str__ wrapper in custom Exception


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'))

Solution

  • 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.