抽象基础 类 和异常
Abstract base classes and Exceptions
问题
为什么使用 ABCMeta.register
创建的摘要 Exception
的虚拟子 class 与 except
子句不匹配?
背景
我想确保将我正在使用的包抛出的异常转换为 MyException
,以便导入我的模块的代码可以使用 [= 捕获我的模块抛出的任何异常17=] 而不是 except Exception
这样他们就不必依赖于实现细节(事实上我正在使用第三方包)。
例子
为此,我尝试使用抽象基础 class:
将 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
原因很简单:
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 子句的结果,但并不代表实际发生的情况。看看
解决方法也很简单:
class OtherException(Exception):
pass
class AnotherException(Exception):
pass
MyException = (OtherException, AnotherException)
好吧,这并没有真正直接回答您的问题,但是如果您试图确保一段代码调用您的异常,您可以采取不同的策略,通过使用上下文管理器进行拦截。
In [78]: class WithException:
...:
...: def __enter__(self):
...: pass
...: def __exit__(self, exc, msg, traceback):
...: if exc is OtherException:
...: raise MyException(msg)
...:
In [79]: with WithException():
...: raise OtherException('aaaaaaarrrrrrggggh')
...:
---------------------------------------------------------------------------
OtherException Traceback (most recent call last)
<ipython-input-79-a0a23168647e> in <module>()
1 with WithException():
----> 2 raise OtherException('aaaaaaarrrrrrggggh')
OtherException: aaaaaaarrrrrrggggh
During handling of the above exception, another exception occurred:
MyException Traceback (most recent call last)
<ipython-input-79-a0a23168647e> in <module>()
1 with WithException():
----> 2 raise OtherException('aaaaaaarrrrrrggggh')
<ipython-input-78-dba8b409a6fd> in __exit__(self, exc, msg, traceback)
5 def __exit__(self, exc, msg, traceback):
6 if exc is OtherException:
----> 7 raise MyException(msg)
8
MyException: aaaaaaarrrrrrggggh
CPython 似乎又一次走捷径,不再为 except
中列出的 classes 调用 metaclass 的 __instancecheck__
方法条款。
我们可以通过使用 __instancecheck__
和 __subclasscheck__
方法实现自定义元 class 来测试它:
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
我们可以看到 metaclass 中的 print
语句没有被执行。
我不知道这是否是 intended/documented 行为。我能找到的最接近相关信息的是 exception handling tutorial:
A class in an except clause is compatible with an exception if it is
the same class or a base class thereof
这是否意味着 classes 必须是 real subclasses(即父 class 必须是subclass 的 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
如果您必须捕获多个没有共同基础的不同异常 class,您可以将 MyException
定义为一个元组:
MyException = (OtherException, AnotherException)
好的,我对此进行了更多调查。答案是它是 Python3 中的一个长期悬而未决的问题(自从第一次发布以来)并且显然是评论中的第一个 reported in 2011. As Guido said,"I agree it's a bug and should be fixed." 不幸的是,这个错误一直存在关注修复的性能和一些需要处理的极端情况。
核心问题是errors.c
中的异常匹配例程PyErr_GivenExceptionMatches
使用PyType_IsSubtype
而不是PyObject_IsSubclass
。由于类型和对象在 python3 中应该是相同的,这相当于一个错误。
我做了一个 PR to python3,它似乎涵盖了线程中讨论的所有问题,但考虑到历史,我不是很乐观它很快就会被合并。我们拭目以待。
问题
为什么使用 ABCMeta.register
创建的摘要 Exception
的虚拟子 class 与 except
子句不匹配?
背景
我想确保将我正在使用的包抛出的异常转换为 MyException
,以便导入我的模块的代码可以使用 [= 捕获我的模块抛出的任何异常17=] 而不是 except Exception
这样他们就不必依赖于实现细节(事实上我正在使用第三方包)。
例子
为此,我尝试使用抽象基础 class:
将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
原因很简单:
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 子句的结果,但并不代表实际发生的情况。看看
解决方法也很简单:
class OtherException(Exception):
pass
class AnotherException(Exception):
pass
MyException = (OtherException, AnotherException)
好吧,这并没有真正直接回答您的问题,但是如果您试图确保一段代码调用您的异常,您可以采取不同的策略,通过使用上下文管理器进行拦截。
In [78]: class WithException:
...:
...: def __enter__(self):
...: pass
...: def __exit__(self, exc, msg, traceback):
...: if exc is OtherException:
...: raise MyException(msg)
...:
In [79]: with WithException():
...: raise OtherException('aaaaaaarrrrrrggggh')
...:
---------------------------------------------------------------------------
OtherException Traceback (most recent call last)
<ipython-input-79-a0a23168647e> in <module>()
1 with WithException():
----> 2 raise OtherException('aaaaaaarrrrrrggggh')
OtherException: aaaaaaarrrrrrggggh
During handling of the above exception, another exception occurred:
MyException Traceback (most recent call last)
<ipython-input-79-a0a23168647e> in <module>()
1 with WithException():
----> 2 raise OtherException('aaaaaaarrrrrrggggh')
<ipython-input-78-dba8b409a6fd> in __exit__(self, exc, msg, traceback)
5 def __exit__(self, exc, msg, traceback):
6 if exc is OtherException:
----> 7 raise MyException(msg)
8
MyException: aaaaaaarrrrrrggggh
CPython 似乎又一次走捷径,不再为 except
中列出的 classes 调用 metaclass 的 __instancecheck__
方法条款。
我们可以通过使用 __instancecheck__
和 __subclasscheck__
方法实现自定义元 class 来测试它:
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
我们可以看到 metaclass 中的 print
语句没有被执行。
我不知道这是否是 intended/documented 行为。我能找到的最接近相关信息的是 exception handling tutorial:
A class in an except clause is compatible with an exception if it is the same class or a base class thereof
这是否意味着 classes 必须是 real subclasses(即父 class 必须是subclass 的 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
如果您必须捕获多个没有共同基础的不同异常 class,您可以将 MyException
定义为一个元组:
MyException = (OtherException, AnotherException)
好的,我对此进行了更多调查。答案是它是 Python3 中的一个长期悬而未决的问题(自从第一次发布以来)并且显然是评论中的第一个 reported in 2011. As Guido said,"I agree it's a bug and should be fixed." 不幸的是,这个错误一直存在关注修复的性能和一些需要处理的极端情况。
核心问题是errors.c
中的异常匹配例程PyErr_GivenExceptionMatches
使用PyType_IsSubtype
而不是PyObject_IsSubclass
。由于类型和对象在 python3 中应该是相同的,这相当于一个错误。
我做了一个 PR to python3,它似乎涵盖了线程中讨论的所有问题,但考虑到历史,我不是很乐观它很快就会被合并。我们拭目以待。