【发布时间】:2018-03-21 02:26:51
【问题描述】:
我正在从事一个项目,我们希望验证是否可以在必要时将参数作为异常引发。我们选择了以下内容:
def is_raisable(exception):
funcs = (isinstance, issubclass)
return any(f(exception, BaseException) for f in funcs)
这处理以下用例,满足我们的需求(目前):
is_raisable(KeyError) # the exception type, which can be raised
is_raisable(KeyError("key")) # an exception instance, which can be raised
但是,对于在旧版本 (2.x) 中可以引发的旧样式类,它会失败。然后我们尝试以这种方式解决它:
IGNORED_EXCEPTIONS = [
KeyboardInterrupt,
MemoryError,
StopIteration,
SystemError,
SystemExit,
GeneratorExit
]
try:
IGNORED_EXCEPTIONS.append(StopAsyncIteration)
except NameError:
pass
IGNORED_EXCEPTIONS = tuple(IGNORED_EXCEPTIONS)
def is_raisable(exception, exceptions_to_exclude=IGNORED_EXCEPTIONS):
funcs_to_try = (isinstance, issubclass)
can_raise = False
try:
can_raise = issubclass(exception, BaseException)
except TypeError:
# issubclass doesn't like when the first parameter isn't a type
pass
if can_raise or isinstance(exception, BaseException):
return True
# Handle old-style classes
try:
raise exception
except TypeError as e:
# It either couldn't be raised, or was a TypeError that wasn't
# detected before this (impossible?)
return exception is e or isinstance(exception, TypeError)
except exceptions_to_exclude as e:
# These are errors that are unlikely to be explicitly tested here,
# and if they were we would have caught them before, so percolate up
raise
except:
# Must be bare, otherwise no way to reliably catch an instance of an
# old-style class
return True
这通过了我们所有的测试,但它不是很漂亮,如果我们正在考虑一些我们不希望用户传递的东西,但它仍然会让人觉得很笨拙,但无论如何可能会被扔进去原因。
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"))
不幸的是,我们需要继续支持 Python 2.6+(很快将只支持 Python 2.7,所以如果您有一个在 2.6 中不起作用的解决方案,那很好,但并不理想)和 Python 3.x。理想情况下,我希望在没有明确测试版本的情况下这样做,但如果没有其他方法可以做到这一点。
最后,我的问题是:
- 有没有更简单的方法可以做到这一点并支持所有列出的版本?
- 如果没有,是否有更好或更安全的方法来处理“特殊异常”,例如
KeyboardInterrupt。 - 作为最 Pythonic 的人,我想请求宽恕而不是许可,但考虑到我们可以获得两种类型的
TypeError(一种是因为它有效,一种是因为它没有),这也让人感觉很奇怪(但无论如何我都必须依靠它来获得 2.x 支持)。
【问题讨论】:
-
"old-style classes [...] are raisable" 不在任何版本中,只有旧版本。
-
@IgnacioVazquez-Abrams 我知道,但不清楚。我会编辑。
-
无论如何,我不会担心它不是
BaseException的后代;当代码移植到 3.x 时,它必须是 1。 -
@Dannnno 啊,在问题中,我只是没有仔细阅读。那好吧。我认为我的回答的精神——你不需要所有那些仔细的 isinstance/issubclass 检查——仍然是正确的,但我可能在不相关的细节上花费了更多的精力而不是重要的东西。当我再次来到电脑前时,我会检查一下。对此感到抱歉。
-
顺便说一句,我喜欢关于异常的 2.7 参考文档为您提供了有关 1.4 和 1.5 之间更改的所有详细信息,但除了 try/ 之外,没有告诉您所有后续更改何时发生除了/最终在 2.5 中合并。何时禁止引发非类型异常?介于 1.0 和 2.6 之间,我猜你只需要知道这些……
标签: python python-3.x exception python-2.x