为什么在Exception
子句下使用ABCMeta.register
创建的抽象except
的虚拟子类不匹配?
我想确保我使用的软件包抛出的异常转换为MyException
,以便导入我的模块的代码可以捕获我的模块抛出的任何异常except MyException:
代替except Exception
,以便他们不必依赖于实施细节(我使用的是第三方软件包)。
为此,我尝试使用抽象基类将OtherException
注册为MyException
:
# Tested with python-3.6
from abc import ABC
class MyException(Exception, ABC):
pass
class OtherException(Exception):
"""Other exception I can't change"""
pass
MyException.register(OtherException)
assert issubclass(OtherException, MyException) # passes
try:
raise OtherException("Some OtherException")
except MyException:
print("Caught MyException")
except Exception as e:
print("Caught Exception: {}".format(e))
断言通过(如预期的那样),但异常落到第二个块:
Caught Exception: Some OtherException
答案 0 :(得分:4)
原因很简单:
from abc import ABC
class MyException(Exception, ABC):
pass
class OtherException(Exception):
"""Other exception I can't change"""
pass
MyException.register(OtherException)
assert issubclass(OtherException, MyException) # passes
assert OtherException in MyException.__subclasses__() # fails
编辑:此assert
模仿except子句的结果,但不代表实际发生的情况。查看accept answer for an explanation.
解决方法也很简单:
class OtherException(Exception):
pass
class AnotherException(Exception):
pass
MyException = (OtherException, AnotherException)
答案 1 :(得分:2)
好的,我更多地研究了这个。答案是,它是Python3中一个长期悬而未决的开放问题(从第一个版本开始),显然是第一个reported in 2011。作为评论中的Guido said,“我同意这是一个错误,应该修复。”不幸的是,由于担心修复程序的性能以及需要处理的一些极端情况,这个bug已经徘徊不前。
核心问题是errors.c
中的异常匹配例程PyErr_GivenExceptionMatches
使用PyType_IsSubtype
而不是PyObject_IsSubclass
。由于python3中的类型和对象应该是相同的,这相当于一个错误。
我做了PR to python3似乎涵盖了线程中讨论的所有问题,但鉴于历史我不是非常乐观,它很快就会合并。我们会看到。
答案 2 :(得分:1)
似乎CPython再次采用了一些快捷方式,并且不会为except
子句中列出的类调用元类的__instancecheck__
方法。
我们可以通过使用__instancecheck__
和__subclasscheck__
方法实现自定义元类来测试这一点:
class OtherException(Exception):
pass
class Meta(type):
def __instancecheck__(self, value):
print('instancecheck called')
return True
def __subclasscheck__(self, value):
print('subclasscheck called')
return True
class MyException(Exception, metaclass=Meta):
pass
try:
raise OtherException("Some OtherException")
except MyException:
print("Caught MyException")
except Exception as e:
print("Caught Exception: {}".format(e))
# output:
# Caught Exception: Some OtherException
我们可以看到元类中的print
语句没有被执行。
我不知道这是否是预期/记录的行为。我能找到的与相关信息最接近的是来自exception handling tutorial:
如果是,则except子句中的类与异常兼容 同一类或基类
这是否意味着类必须是真正的子类(即父类必须是子类的MRO的一部分)?我不知道。
至于解决方法:您只需将MyException
设为OtherException
的别名。
class OtherException(Exception):
pass
MyException = OtherException
try:
raise OtherException("Some OtherException")
except MyException:
print("Caught MyException")
except Exception as e:
print("Caught Exception: {}".format(e))
# output:
# Caught MyException
如果您必须捕获多个不具有公共基类的不同异常,则可以将MyException
定义为元组:
MyException = (OtherException, AnotherException)
答案 3 :(得分:0)
嗯,这并没有直接回答你的问题,但是如果你试图确保代码块调用你的异常,你可以通过拦截上下文管理器采取不同的策略。
from django.utils.encoding import smart_text
...
def __str__(self):
return smart_text(self.email)