抽象基类和异常

时间:2018-04-12 16:16:45

标签: python python-3.x abc

问题

为什么在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

4 个答案:

答案 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)