gpt4 book ai didi

python - 检查是否可以在任何版本中引发某些问题

转载 作者:太空宇宙 更新时间:2023-11-03 13:08:47 25 4
gpt4 key购买 nike

我正在做一个项目,我们想要验证一个参数是否可以在必要时作为异常实际引发。我们采用了以下方法:

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。理想情况下,我想在不对版本进行显式测试的情况下执行此操作,但如果没有其他方法可以执行此操作,那也没关系。

最后,我的问题是:

  1. 是否有更简单的方法来执行此操作并支持所有列出的版本?
  2. 如果不是,是否有更好或更安全的方法来处理“特殊异常”,例如键盘中断
  3. 为了最 Pythonic,我想请求原谅而不是许可,但考虑到我们可以获得两种类型的 TypeError(一种因为它有效,一种因为它无效)感觉也很奇怪(但为了 2.x 支持,我不得不退而求其次)。

最佳答案

在 Python 中测试大多数事情的方法是尝试,然后看看是否出现异常。

这对 raise 很有效。如果某些内容不可引发,您将得到一个 TypeError;否则,您将得到您筹集的资金(或您筹集的资金的一个实例)。这对 2.6(甚至 2.3)和 3.6 一样有效。字符串作为 2.6 中的异常将被引发;不从 3.6 中的 BaseException 继承的类型将不可引发;等等——你会得到正确的结果。无需检查 BaseException 或以不同方式处理旧式和新式类;让 raise 做它做的事。

当然我们确实需要特例TypeError,因为它会落在错误的地方。但是因为我们不关心 pre-2.4,所以不需要比 isinstanceissubclass 测试更复杂的东西;除了 return False 之外,没有奇怪的对象可以做任何事情了。一个棘手的问题(我最初弄错了;感谢 user2357112 捕获了它)是你必须先进行 isinstance 测试,因为如果对象是 TypeError例如,issubclass 将引发 TypeError,因此我们需要短路并返回 True 而无需尝试。

另一个问题是处理我们不想意外捕获的任何特殊异常,例如 KeyboardInterruptSystemError。但幸运的是,these all go back to before 2.6 .两者都是isinstance/issubclassexcept clauses (只要您不关心捕获异常值,我们不关心)可以使用在 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 版本中可提升的对象为真,而不是在任何支持的版本中可提升的对象> 即使它们在当前版本中不可提升。您不希望 is_raisable('spam') 在 3.6 中返回 True 然后尝试 raise 'spam' 会失败,对吧?所以,在我的脑海中:

  • not_raisable 测试引发了一个字符串——但在 2.6 中是可引发的。
  • excluded_type 测试引发了一个类,Python 2.x 可以通过实例化该类来处理该类,但这不是必需的,并且 CPython 2.6 进行了优化将在这种情况下触发。
  • old_style 测试在 3.6 中引发新式类,它们不是 BaseException 的子类,因此它们不可引发。

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

关于python - 检查是否可以在任何版本中引发某些问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49397153/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com