检查是否可以在任何版本中引发某些问题
Check if something is raisable in any version
我正在做一个项目,我们想要验证一个参数是否可以在必要时作为异常实际引发。我们采用以下方法:
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。理想情况下,我想在不对版本进行显式测试的情况下执行此操作,但如果没有其他方法可以执行此操作,那也没关系。
最后,我的问题是:
- 有没有更简单的方法来做到这一点并支持所有列出的版本?
- 如果没有,是否有更好或更安全的方法来处理 "special exceptions",例如
KeyboardInterrupt
.
- 最 Pythonic 我想请求原谅而不是许可,但考虑到我们可以获得两种类型的
TypeError
(一种因为有效,另一种因为无效't) 感觉也很奇怪(但为了 2.x 支持,我不得不退回到那个)。
你在 Python 中测试大多数东西的方法是 try
然后看看你是否得到异常。
这对 raise
很有效。如果无法筹集资金,您将获得 TypeError
;否则,您将得到您筹集的资金(或您筹集的资金的一个实例)。这对 2.6(甚至 2.3)和 3.6 一样有效。字符串作为 2.6 中的异常将被引发;不从 3.6 中的 BaseException
继承的类型将不可提升;等等——你会得到正确的结果。无需检查 BaseException
或以不同方式处理 old-style 和 new-style class;就让 raise
做它做的吧。
当然我们确实需要 special-case TypeError
,因为它会落在错误的地方。但是因为我们不关心 pre-2.4,所以不需要比 isinstance
和 issubclass
测试更复杂的东西;除了 return False
之外,没有任何奇怪的对象可以做任何事情了。一个棘手的问题(我最初弄错了;感谢 user2357112 抓住了它)是你必须先进行 isinstance
测试,因为如果对象是 TypeError
实例,issubclass
会提高 TypeError
,所以我们需要 short-circuit 和 return True
而不是尝试。
另一个问题是处理我们不想意外捕获的任何特殊异常,例如 KeyboardInterrupt
和 SystemError
。但幸运的是,these all go back to before 2.6. And both isinstance
/issubclass
and except
clauses(只要您不关心捕获异常值,我们不关心)可以采用在 3.x 中也适用的语法的元组。由于这些情况要求我们 return True
,因此我们需要在尝试提出它们之前对其进行测试。但它们都是 BaseException
subclasses,所以我们不必担心 classic classes 或类似的东西。
所以:
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 版本 中可提升的对象 为真,而不是在 中可提升的对象任何受支持的版本,即使它们在当前版本中不可升级。您不希望在 3.6 中 is_raisable('spam')
到 return True
然后尝试 raise 'spam'
会失败,对吗?所以,在我的脑海中:
not_raisable
测试引发了一个字符串——但这些在 2.6 中是可以引发的。
excluded_type
测试引发了一个 class,Python 2.x 可以 通过实例化 class 来处理],但这不是必需的,并且 CPython 2.6 具有将在这种情况下触发的优化。
old_style
测试在 3.6 中提高 new-style classes,它们不是 BaseException
的子classes,所以它们是不可筹集。
我不确定如何在不为 2.6、3.x 甚至 2.7,甚至可能为两个 2.x 版本的不同实现编写单独测试的情况下编写正确的测试(尽管可能您没有任何用户,比如 Jython?)。
如果你想检测 old-style 类 和实例,只需对它们进行显式检查:
import types
if isinstance(thing, (types.ClassType, types.InstanceType)):
...
您可能希望将它包装在某种版本检查中,这样它就不会在 Python 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
我正在做一个项目,我们想要验证一个参数是否可以在必要时作为异常实际引发。我们采用以下方法:
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。理想情况下,我想在不对版本进行显式测试的情况下执行此操作,但如果没有其他方法可以执行此操作,那也没关系。
最后,我的问题是:
- 有没有更简单的方法来做到这一点并支持所有列出的版本?
- 如果没有,是否有更好或更安全的方法来处理 "special exceptions",例如
KeyboardInterrupt
. - 最 Pythonic 我想请求原谅而不是许可,但考虑到我们可以获得两种类型的
TypeError
(一种因为有效,另一种因为无效't) 感觉也很奇怪(但为了 2.x 支持,我不得不退回到那个)。
你在 Python 中测试大多数东西的方法是 try
然后看看你是否得到异常。
这对 raise
很有效。如果无法筹集资金,您将获得 TypeError
;否则,您将得到您筹集的资金(或您筹集的资金的一个实例)。这对 2.6(甚至 2.3)和 3.6 一样有效。字符串作为 2.6 中的异常将被引发;不从 3.6 中的 BaseException
继承的类型将不可提升;等等——你会得到正确的结果。无需检查 BaseException
或以不同方式处理 old-style 和 new-style class;就让 raise
做它做的吧。
当然我们确实需要 special-case TypeError
,因为它会落在错误的地方。但是因为我们不关心 pre-2.4,所以不需要比 isinstance
和 issubclass
测试更复杂的东西;除了 return False
之外,没有任何奇怪的对象可以做任何事情了。一个棘手的问题(我最初弄错了;感谢 user2357112 抓住了它)是你必须先进行 isinstance
测试,因为如果对象是 TypeError
实例,issubclass
会提高 TypeError
,所以我们需要 short-circuit 和 return True
而不是尝试。
另一个问题是处理我们不想意外捕获的任何特殊异常,例如 KeyboardInterrupt
和 SystemError
。但幸运的是,these all go back to before 2.6. And both isinstance
/issubclass
and except
clauses(只要您不关心捕获异常值,我们不关心)可以采用在 3.x 中也适用的语法的元组。由于这些情况要求我们 return True
,因此我们需要在尝试提出它们之前对其进行测试。但它们都是 BaseException
subclasses,所以我们不必担心 classic classes 或类似的东西。
所以:
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 版本 中可提升的对象 为真,而不是在 中可提升的对象任何受支持的版本,即使它们在当前版本中不可升级。您不希望在 3.6 中 is_raisable('spam')
到 return True
然后尝试 raise 'spam'
会失败,对吗?所以,在我的脑海中:
not_raisable
测试引发了一个字符串——但这些在 2.6 中是可以引发的。excluded_type
测试引发了一个 class,Python 2.x 可以 通过实例化 class 来处理],但这不是必需的,并且 CPython 2.6 具有将在这种情况下触发的优化。old_style
测试在 3.6 中提高 new-style classes,它们不是BaseException
的子classes,所以它们是不可筹集。
我不确定如何在不为 2.6、3.x 甚至 2.7,甚至可能为两个 2.x 版本的不同实现编写单独测试的情况下编写正确的测试(尽管可能您没有任何用户,比如 Jython?)。
如果你想检测 old-style 类 和实例,只需对它们进行显式检查:
import types
if isinstance(thing, (types.ClassType, types.InstanceType)):
...
您可能希望将它包装在某种版本检查中,这样它就不会在 Python 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