自定义异常中的 __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__ 包装器的主要内容,如果未能解决你的问题,请参考以下文章