Python:如何知道方法调用可能引发哪些异常


87

有没有一种方法(在编码时)知道在执行python代码时期望哪些异常?由于我不知道可能抛出哪种异常类型(并且不要告诉我阅读文档,所以很多时候我都会捕获90%的基类Exception。很多时候,可以从深处传播异常。)次文档未更新或不正确)。有某种工具可以检查吗?(例如通过阅读python代码和库)?


2
请记住,在Python <2.6中,您还可以raise字符串,而不仅仅是BaseException子类。因此,如果您要调用的代码超出了您的控制范围,那甚至except Exception是不够的,因为它不会捕获字符串异常。正如其他人指出的那样,您在这里树错了树。
Daniel Pryden 09年

我不知道 我以为除了Exception:..几乎捕获了所有内容。
GabiMe,2009年

2
except Exception在Python 2.6及更高版本中捕获字符串异常的效果很好。
杰弗里·哈里斯

Answers:


22

我猜一个解决方案可能只是不精确,因为缺少静态的输入规则。

我不知道有一些检查异常的工具,但是您可以根据自己的需求提出自己的工具(这是一个很好的机会来进行静态分析)。

第一次尝试,您可以编写一个构建AST的函数,查找所有Raise节点,然后尝试找出引发异常的常见模式(例如,直接调用构造函数)

x下面的程序为例:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

使用compiler包构建AST :

tree = compiler.parse(x)

然后定义一个Raise访问者类:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

并遍历AST收集Raise节点:

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

您可以继续使用编译器符号表解析符号,分析数据依赖关系等。或者您可以推断出CallFunc(Name('IOError'), ...)“绝对应该意味着加注IOError”,对于快速实用的结果来说是可以的:)


感谢您提供有趣的答案。我不明白为什么我除了寻找所有的筹款节点外,还要寻找更多的东西。为什么要“使用编译器符号表解析符号,分析数据依赖性”?引发异常的唯一方法不是通过raise()吗?
GabiMe

1
给定v.nodes上面的值,您实际上不能说什么是Name('IOError')Name('e')。你不知道什么样的价值(S)的IOErrore可以指向,因为他们是所谓的自由变量。即使知道它们的绑定上下文(在这里符号表起作用),您也应该执行某种数据依赖关系分析以推断出它们的确切值(这在Python中应该很难做到)。
2009年

当您在寻找实用的半自动化解决方案时,['IOError(errno.ENOENT, "not found")', 'e']向用户显示的列表就可以了。但是您不能推断出由字符串表示的变量的值的实际类别:)(很抱歉重新发布)
Andrey Vlasovskikh 2009年

1
是。这种方法虽然很聪明,但实际上并不能完全覆盖您。由于Python具有动态特性,因此您调用的代码完全有可能(尽管显然是个坏主意)来执行类似的操作exc_class = raw_input(); exec "raise " + exc_class。关键是,在像Python这样的动态语言中,这种静态分析是不可能实现的。
丹尼尔·普里登

7
顺便说一句,您只能find /path/to/library -name '*.py' | grep 'raise '得到类似的结果:)
Andrey Vlasovskikh

24

您应该只捕获将要处理的异常。

按照具体类型捕获所有异常是胡说八道。您应该捕获可以并且将要处理的特定异常。对于其他异常,您可以编写一个通用捕获来捕获“基本异常”,将其记录(使用str()函数)并终止程序(或执行其他在崩溃情况下适当的操作)。

如果您真的要处理所有异常,并确保它们都不是致命的(例如,如果您在某种沙盒环境中运行代码),那么捕获通用BaseException的方法就可以满足您的目标。

您可能还对语言异常参考感兴趣,而不是所用库的参考。

如果库引用确实很差,并且在捕获系统异常时没有重新抛出其自身的异常,则唯一有用的方法是运行测试(可能将其添加到测试套件中,因为如果未记录某些内容,则可能会更改!) 。删除对您的代码至关重要的文件,然后检查抛出了什么异常。提供过多的数据,并检查其产生什么错误。

无论如何,您都将必须运行测试,因为即使存在通过源代码获取异常的方法,也不会让您知道如何处理这些异常。也许您应该显示错误消息“找不到文件needful.txt!” 什么时候抓到IndexError?只有测试可以证明。


26
当然可以,但是如果他不知道会抛出什么异常,那该如何决定他应该处理哪些异常呢?
GabiMe,2009年

@ bugspy.net,修复了我的回答以反映此问题
P Shved

也许现在是时候让源代码分析器知道这一点了吗?我认为应该不难开发
GabiMe,2009年

@ bugspy.net,我在条款中加粗了为什么它可能不是时候。
P Shved

确定你是对的。但是,在开发过程中了解哪些异常类型可能会仍然很有趣。
hek2mgl

11

解决此问题的正确工具是单元测试。如果您在真实代码中引发了单元测试不会引发的异常,那么您需要更多的单元测试。

考虑一下

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

鸭子可以是任何物体

显然,您可以拥有一个AttributeErrorif鸭没有嘎嘎声,一个TypeErrorif鸭有嘎嘎声,但是它不能被召唤。您不知道duck.quack()可能会发生什么,甚至可能是DuckError什么

现在假设您有这样的代码

arr[i] = get_something_from_database()

如果它引发a IndexError,则不知道它是来自arr [i]还是来自数据库函数内部。通常,异常发生的位置并不重要,而出了什么问题以及您想发生的事情却没有发生。

一个方便的技术是捕获并提出这样的异常

except Exception as e
    #inspect e, decide what to do
    raise

如果要“提高”它,为什么还要抓住它呢?
塔尔奈·卡曼(TarnayKálmán),2009年

你不必到再加注它,那就是有一种意见认为应该做的指示。
约翰·拉鲁伊

2
您也可以选择将异常记录到某个地方,然后重新引发
John La Rooy

2
我认为编写单元测试不是答案。问题是“我如何找出期望的异常”,编写单元测试将无法帮助您找到答案。实际上,为了编写单元测试,您已经必须知道期望哪些异常,因此,为了编写正确的单元测试,您还必须回答原始问题。
布鲁诺·兰斯哈特

6

到目前为止,没有人解释,为什么您不能获得完整的100%正确的例外列表,所以我认为值得一提。原因之一是一流的功能。假设您有一个像这样的函数:

def apl(f,arg):
   return f(arg)

现在apl可以引发任何f引发的异常。虽然在核心库中没有很多类似的功能,但是使用自定义过滤器,映射,归约等使用列表理解的任何内容都会受到影响。

文档和源分析器是这里唯一的“大量”信息源。只要记住他们不能做什么。


4

我在使用套接字时遇到了这个问题,我想找出我将要遇到的所有错误情况(因此,与其尝试创建错误并弄清楚我只想要一个简洁的列表,不如尝试创建错误)。最终,我最终将“ /usr/lib64/python2.4/test/test_socket.py”替换为“ raise”:

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

这是一个非常简洁的错误列表。当然,现在这仅取决于具体情况,并且取决于测试的准确性(通常是正确的)。否则,您几乎需要捕获所有异常,将其记录下来并进行剖析,并弄清楚如何处理它们(使用单元测试就不那么困难了)。


4
这强化了我的观点,即如果我们需要使用grep或源分析器来处理如此基本的东西(例如,从一开始就存在于Java中),那么Python中的异常处理就非常有问题。有时冗长是一件好事。Java冗长但至少没有令人讨厌的惊喜)
GabiMe,2009年

@GabiMe,这不是防止所有错误的灵丹妙药(或通常是静态键入)。Java充满了令人讨厌的惊喜。这就是为什么月食经常崩溃的原因。
John La Rooy

2

我发现有两种方法可以提供信息。第一个,在iPython中运行代码,它将显示异常类型。

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

第二种方法是我们适应于捕获过多的漏洞,并随着时间的推移不断改进。try在您的代码中包含一个表达式并catch except Exception as err。打印足够的数据以了解引发了什么异常。当抛出异常时,通过添加更精确的except子句来改进代码。当您感觉已捕获所有相关异常时,请删除所有包含的异常。无论如何都是一件好事,因为它会吞噬编程错误。

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)

1

通常,您只需捕获几行代码即可捕获异常。您不想将整个main函数放到该try except子句中。对于每行几行,您现在始终应该(或能够轻松检查)可能引发的异常类型。

docs有详尽的内置异常列表。不要尝试除您不期望的那些异常之外,它们可能在调用代码中得到处理/预期。

编辑:可能会抛出什么显然取决于您在做什么!访问sequence:的IndexError随机元素,dict:的随机元素KeyError,等等。

只需尝试在IDLE中运行这几行并引起异常。但是,单元测试自然是一个更好的解决方案。


1
这没有回答我的简单问题。我不问如何设计我的异常处理,或何时或如何捕获。我问如何找出可能引发的事件
GabiMe,2009年

1
@ bugspy.net:这是不可能完成的,这是一个完全有效的解决方法。
丹尼尔·普里登
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.