检查在任何版本中是否有什么东西是可以提升的

2024-03-29 11:27:58 发布

您现在位置:Python中文网/ 问答频道 /正文

我在一个项目中工作,我们想验证一个参数是否可以在必要时作为异常引发。我们进行了以下操作:

def is_raisable(exception):
    funcs = (isinstance, issubclass)
    return any(f(exception, BaseException) for f in funcs)

这将处理以下满足我们需求的用例(目前):

^{pr2}$

但是,对于旧样式的类,它失败了,因为旧版本(2.x)中可以对其进行提升。我们试着用这种方法解决这个问题:

^{3}$

这通过了我们所有的测试,但是它不是很漂亮,而且如果我们考虑一些我们不希望用户通过的东西,但是可能会因为其他原因被扔进去,那么它仍然会让人觉得很不舒服。在

def test_is_raisable_exception(self):
    """Test that an exception is raisable."""

    self.assertTrue(is_raisable(Exception))

def test_is_raisable_instance(self):
    """Test that an instance of an exception is raisable."""

    self.assertTrue(is_raisable(Exception()))

def test_is_raisable_old_style_class(self):
    """Test that an old style class is raisable."""

    class A: pass

    self.assertTrue(is_raisable(A))

def test_is_raisable_old_style_class_instance(self):
    """Test that an old style class instance is raisable."""

    class A: pass

    self.assertTrue(is_raisable(A()))

def test_is_raisable_excluded_type_background(self):
    """Test that an exception we want to ignore isn't caught."""

    class BadCustomException:
        def __init__(self):
            raise KeyboardInterrupt

    self.assertRaises(KeyboardInterrupt, is_raisable, BadCustomException)

def test_is_raisable_excluded_type_we_want(self):
    """Test that an exception we normally want to ignore can be not
    ignored."""

    class BadCustomException:
        def __init__(self):
            raise KeyboardInterrupt

    self.assertTrue(is_raisable(BadCustomException, exceptions_to_exclude=()))

def test_is_raisable_not_raisable(self):
    """Test that something not raisable isn't considered rasiable."""

    self.assertFalse(is_raisable("test"))

不幸的是,我们需要继续支持Python2.6+(很快就只支持Python2.7,所以如果你有一个在2.6中不起作用的解决方案,那很好,但并不理想)和Python3.x。理想的情况下,我希望在没有显式测试版本的情况下这样做,但是如果没有其他方法,那就没问题了。在

最后,我的问题是:

  1. 有没有更简单的方法来支持所有列出的版本?在
  2. 如果不是,是否有更好或更安全的方法来处理“特殊异常”,例如KeyboardInterrupt。在
  3. 作为最具Python的人,我想请求原谅而不是允许,但是考虑到我们可以得到两种类型的TypeError(一种是因为它有效,另一种是因为它不起作用),这也让人觉得奇怪(但无论如何,我还是得依靠它来获得2.x的支持)。在

Tags: 方法instancetestselfanthatisstyle
3条回答

如果要检测旧样式的类和实例,只需显式检查它们:

import types

if isinstance(thing, (types.ClassType, types.InstanceType)):
    ...

您可能希望将其包装在某种版本检查中,这样它就不会在python3上失败。在

您可以引发对象,捕获异常,然后使用is关键字检查引发的异常是否是对象或对象的实例。如果引发了其他任何内容,则为TypeError,表示该对象不可提升。在

此外,要处理任何可提升的对象,我们可以使用^{}。这还将捕捉异常,例如KeyboardInterrupt,但是如果与参数的比较没有结果,我们可以重新调用它们。在

import sys

def is_raisable(obj):
    try:
        raise obj
    except:
        exc_type, exc = sys.exc_info()[:2]

        if exc is obj or exc_type is obj:
            return True
        elif exc_type is TypeError:
            return False
        else:
            # We reraise exceptions such as KeyboardInterrupt that originated from outside
            raise

is_raisable(ValueError) # True
is_raisable(KeyboardInterrupt) # True
is_raisable(1) # False

在Python中测试大多数东西的方法是try,然后查看是否出现异常。在

这对raise很好。如果某些内容不可引发,则会得到一个TypeError;否则,您将得到您所引发的内容(或您所引发内容的实例)。这对2.6(甚至2.3)和3.6一样有效。在2.6中作为异常的字符串将是可raise的;不从3.6中的BaseException继承的类型将不可raise;等等-您可以为所有内容获得正确的结果。不需要检查BaseException或以不同的方式处理旧样式和新样式的类;只需让raise完成它的工作。在

当然我们需要特殊情况TypeError,因为它会降落在错误的地方。但是,由于我们不关心2.4之前的版本,因此不需要任何比isinstanceissubclass更复杂的测试;除了返回False之外,再也没有什么奇怪的对象可以做了。一个棘手的地方(我最初弄错了;感谢user2357112捕捉到它)是您必须首先执行isinstance测试,因为如果对象是TypeError实例,issubclass将引发{},因此我们需要短路并返回True,而不进行尝试。在

另一个问题是处理我们不想意外捕获的任何特殊异常,比如KeyboardInterrupt和{}。但幸运的是,these all go back to before 2.6。而且^{}/^{}和{a3}(只要你不关心捕捉异常值,我们不关心)都可以使用同样适用于3.x的语法获取元组。因为对于这些情况,我们需要返回True,所以我们需要在尝试提升它们之前测试它们。但它们都是BaseException子类,所以我们不必担心经典类或类似的东西。在

所以:

def is_raisable(ex, exceptions_to_exclude=IGNORED_EXCEPTIONS):
    try:
        if isinstance(ex, TypeError) or issubclass(ex, TypeError):
            return True
    except TypeError:
        pass
    try:
        if isinstance(ex, exceptions_to_exclude) or issubclass(ex, exceptions_to_exclude):
            return True
    except TypeError:
        pass
    try:
        raise ex
    except exceptions_to_exclude:
        raise
    except TypeError:
        return False
    except:
        return True

这并不能通过您编写的测试套件,但我认为这是因为您的一些测试不正确。我假设您希望is_raisable对当前Python版本中可raise的对象为true,而不是在任何受支持的版本中可raise的对象,即使它们在当前版本中不可raise。你不会希望is_raisable('spam')在3.6中返回{},然后尝试{}会失败,对吧?所以,在我的头顶上:

  • not_raisable测试会引发一个字符串,但是在2.6中这些字符串是可以被提升的。在
  • excluded_type测试引发了一个类,python2.x可以通过实例化类来处理这个类,但这不是必需的,cpython2.6在这种情况下会触发优化。在
  • old_style测试在3.6中生成了新样式的类,并且它们不是BaseException的子类,因此它们不能被提升。在

我不知道如果不为2.6、3.x甚至2.7编写单独的测试,甚至可能为两个2.x版本的不同实现编写正确的测试(尽管可能您在Jython上没有任何用户)。在

相关问题 更多 >