自定义异常中的 __str__ 包装器

Posted

技术标签:

【中文标题】自定义异常中的 __str__ 包装器【英文标题】:__str__ wrapper in custom Exception 【发布时间】:2022-01-03 16:48:54 【问题描述】:

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

【问题讨论】:

__str__ 在类 CustomException 上调用,而不是在单个对象上调用。您只是在单个对象 self 上更改 __str__ 您是否有理由尝试对__str__ 进行包装和替换,而不是仅仅重新实现__str__ 方法 并调用super().__str__() @khelwood 是的,我完全错过了,感谢您的提示。宫城先生,不,除了把事情复杂化之外没有别的原因 :) 我会按照你的方式做,也谢谢! @khelwood __str__ 是在 CustomException 类上调用的,而不是在单个对象上调用的。 我有点困惑。 __str__ 不是实例方法吗? @TheScore 它是一个实例方法,因为实例是它的第一个参数;但它是在类上查找的,并调用将实例作为第一个参数传递。通常这是调用魔法方法的方式。 【参考方案1】:

暂时忘记 Exception 类。考虑一下:

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:

对于自定义类,特殊方法的隐式调用仅 如果在对象的类型上定义,则保证正常工作,而不是在 对象的实例字典。

你想要达到的是:

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

【讨论】:

【参考方案2】:

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

这可以通过以下方式完成: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__ 方法。

【讨论】:

【参考方案3】:

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

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__()

【讨论】:

第一个示例并没有真正按预期工作,因为您创建的每个CustomException 实例都会改变__str__所有 个实例的工作方式。 @chepner 如何解决这个问题? 通过正确定义一个名为__str__的实例方法,而不是创建一个名为__str__的实例属性。 如果你也想让第一个例子工作(我不推荐),你应该把self._wrapper(self.__str__)改为self._wrapper(super().__str__),否则你创建的CustomException('error msg')对象越多你得到的越多ABC 在输出中。测试一下

以上是关于自定义异常中的 __str__ 包装器的主要内容,如果未能解决你的问题,请参考以下文章

自定义异常处理

python自定义异常

自定义抛异常

自定义异常

自定义一个异常

python__基础 : 异常处理与自定义异常