我正在开发一个项目,我们想要验证参数实际上可以在必要时作为异常引发。我们选择了以下内容:
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
。TypeError
(一种因为它有效,一种因为它没有#)这也感觉很奇怪(但无论如何我必须依靠它来获得2.x支持)。答案 0 :(得分:3)
你在Python中测试大多数东西的方法是try
,然后看看你是否得到了例外。
适用于raise
。如果某些东西不可升级,你将获得TypeError
;否则,你会得到你所筹集的东西(或你所筹集的东西的实例)。这将适用于2.6(甚至2.3)和3.6。作为2.6中的例外的字符串将是可升级的;不在3.6中从BaseException
继承的类型将不会升级;等等 - 你得到了一切正确的结果。无需检查BaseException
或以不同方式处理旧式和新式类;让raise
做它做的事情。
当然我们需要特殊情况TypeError
,因为它会落在错误的地方。但由于我们不关心2.4之前的版本,因此不需要比isinstance
和issubclass
测试更复杂的事情;除了返回False
之外,没有任何奇怪的对象可以做任何事情。一个棘手的位(我最初错了;感谢user2357112用于捕获它)是你必须首先进行isinstance
测试,因为如果对象是TypeError
实例,{{1} }会引发issubclass
,所以我们需要短路并返回TypeError
而不尝试。{/ p>
另一个问题是处理我们不想意外捕获的任何特殊例外,例如True
和KeyboardInterrupt
。但幸运的是,these all go back to before 2.6。并且isinstance
/issubclass
和except
clauses(只要你不关心捕获异常值,我们没有)可以使用在3.x中工作的语法的元组。由于我们需要为这些情况返回SystemError
,因此我们需要在尝试提升它们之前对其进行测试。但它们都是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
对于当前Python版本中可升级的对象是真的,而不是任何支持版本中可升级的对象它们在当前版本中不会升级。您不希望is_raisable
在3.6中返回is_raisable('spam')
,然后尝试True
会失败,对吧?所以,脱离我的头脑:
raise 'spam'
测试会引发一个字符串 - 但这些字符串在2.6中可以升级。not_raisable
测试引发了一个类,Python 2.x 可以通过实例化类来处理它,但它不是必需的,并且CPython 2.6具有将触发的优化这种情况。excluded_type
测试会在3.6中引发新样式的类,而它们不是old_style
的子类,所以它们不会升级。我不确定如何编写正确的测试而不编写2.6,3.x甚至2.7的单独测试,甚至可能针对两个2.x版本的不同实现(尽管可能你没有比如Jython上的任何用户?)。
答案 1 :(得分:3)
您可以引发对象,捕获异常,然后使用is
关键字检查引发的异常是对象还是对象的实例。如果还有其他内容,那就是TypeError
意味着对象不可升级。
此外,为了处理绝对任何可升级的对象,我们可以使用sys.exc_info
。这也会捕获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
答案 2 :(得分:1)
如果要检测旧式类和实例,只需对它们进行明确检查:
import types
if isinstance(thing, (types.ClassType, types.InstanceType)):
...
您可能希望将其包装在某种版本检查中,以便在Python 3上不会失败。