__str__ 自定义异常中的包装器

__str__ wrapper in custom Exception

为什么下面的代码打印 error msg 而不是 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'))

暂时忘记异常 class。考虑一下:

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)

输出:

A
A   # Not B !
B

Dunder 方法的查找在 Python 中有所不同。来自 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.

你想要达到的是:

class CustomException(Exception):
    """ABC"""

    def __init__(self, *args):
        super().__init__(*args)

    def __str__(self):
        return self.__doc__ + '\n' + super().__str__()


print(CustomException('error msg'))

输出:

ABC
error msg

根据 @khelwood 在评论中的回复,此代码按预期工作:

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

但我最终得到了这个等价物 (@MisterMiyagi):

class CustomException(Exception):
    """ABC"""
    def __str__(self):
        return self.__doc__ + '\n' + super().__str__()

由特殊方法支持的操作通常将特殊方法显式查找为正确方法,而不仅仅是可调用属性。具体来说,解释器不是 self.__str__,而是粗略地查看 type(self).__str__.__get__(self, type(self))——即 a descriptor __str__ on the class to be bound with the instance. 要覆盖特殊方法,因此有必要覆盖 class' 描述符而不是实例' 属性。

这可以通过 a) 将特殊方法声明为处理 type(self).__str__ 部分的槽,以及 b) 分配一个处理 __get__(self, type(self)) 部分的函数来完成。

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

请注意,由于在这种情况下每个实例的行为都相同,因此建议在实践中只定义一个新的 __str__ 方法。