如何查找Python中某个库函数可能抛出的所有异常列表?

16 投票
4 回答
3815 浏览
提问于 2025-04-15 22:45

抱歉标题有点长,但我觉得这样描述我的问题最清楚。

简单来说,我在官方的Python文档中找不到异常信息,感觉挺困难的。比如,我正在写的一个程序中,我使用了shutil库的move函数:

from shutil import move
move('somefile.txt', '/tmp/somefile.txt')

这个函数运行得很好,只要我对/tmp/目录有写入权限,磁盘空间足够,并且满足其他所有要求。

不过,当我写一些通用代码时,通常很难保证这些条件都成立,所以一般会用到异常处理:

from shutil import move
try:
    move('somefile.txt', '/tmp/somefile.txt')
except:
    print 'Move failed for some reason.'

我希望能捕捉到具体的异常,而不是随便捕捉所有异常,但我就是找不到大多数Python模块抛出的异常列表。有没有办法让我知道某个函数可能抛出哪些异常,以及原因是什么?这样我就可以为每个异常写出合适的处理方式,比如:

from shutil import move
try:
    move('somefile.txt', '/tmp/somefile.txt')
except PermissionDenied:
    print 'No permission.'
except DestinationDoesNotExist:
    print "/tmp/ doesn't exist"
except NoDiskSpace:
    print 'No diskspace available.'

如果有人能给我提供一些相关的文档链接,或者告诉我如何准确找出哪些函数会抛出哪些异常,以及原因,那我会非常感激。

谢谢!

更新:从大家的回答来看,似乎没有一种100%简单的方法来确定特定函数抛出的错误。通过元编程,我可以找出一些简单的情况并列出一些异常,但这并不是特别有用或方便的方法。

我希望将来能有一个标准,明确每个Python函数会抛出哪些异常,并且这些信息能包含在官方文档中。在那之前,我想我会让这些异常直接抛出,让用户看到错误,这样做似乎是最合理的选择。

4 个回答

4

是的,你可以这样做(对于简单的情况),但需要一点元编程的知识。就像其他回答提到的,函数并不会声明它会抛出特定类型的错误,所以你需要查看这个模块,看看它定义了哪些异常类型,或者它会抛出哪些异常类型。你可以尝试理解文档,或者利用Python的API来做到这一点。

首先,要找出一个模块定义了哪些异常类型,你只需要写一个简单的脚本,遍历模块字典 module.__dict__ 中的每个对象,看看它的名字是否以“Error”结尾,或者它是否是Exception的子类:

def listexns(mod):
    """Saved as: http://gist.github.com/402861
    """
    module = __import__(mod)
    exns = []
    for name in module.__dict__:
        if (issubclass(module.__dict__[name], Exception) or
            name.endswith('Error')):
            exns.append(name)
    for name in exns:
        print '%s.%s is an exception type' % (str(mod), name)
    return

如果我在你的例子中运行 shutils,我得到的结果是:

$ python listexn.py shutil
Looking for exception types in module: shutil
shutil.Error is an exception type
shutil.WindowsError is an exception type
$

这告诉你定义了哪些错误类型,但并没有告诉你哪些错误类型会被抛出。要做到这一点,我们需要遍历Python解释器解析模块时生成的抽象语法树,查找每个 raise 语句,然后保存被抛出的名称列表。这个代码稍微长一点,所以我先给出输出结果:

$ python listexn-raised.py /usr/lib/python2.6/shutil.py
Looking for exception types in: /usr/lib/python2.6/shutil.py
/usr/lib/python2.6/shutil.py:OSError is an exception type
/usr/lib/python2.6/shutil.py:Error is an exception type
$ 

现在我们知道 shutil.py 定义了错误类型 ErrorWindowsError,并且抛出了异常类型 OSErrorError。如果我们想更全面一点,可以再写一个方法,检查每个 except 子句,看看 shutil 处理了哪些异常。

下面是遍历抽象语法树的代码,它使用 compiler.visitor 接口创建一个遍历器,采用了《设计模式》一书中的“访问者模式”:

class ExceptionFinder(visitor.ASTVisitor):
    """List all exceptions raised by a module. 
    Saved as: http://gist.github.com/402869
    """

    def __init__(self, filename):
        visitor.ASTVisitor.__init__(self)
        self.filename = filename
        self.exns = set()
        return

    def __visitName(self, node):
        """Should not be called by generic visit, otherwise every name
        will be reported as an exception type.
        """
        self.exns.add(node.name)
        return

    def __visitCallFunc(self, node):
        """Should not be called by generic visit, otherwise every name
        will be reported as an exception type.
        """
        self.__visitName(node.node)
        return

    def visitRaise(self, node):
        """Visit a raise statement.
        Cheat the default dispatcher.
        """
        if issubclass(node.expr1, compiler.ast.Name):
            self.__visitName(node.expr1)
        elif isinstance(node.expr1, compiler.ast.CallFunc):
            self.__visitCallFunc(node.expr1)
        return
5

目前,Python 没有像 Java 那样的机制来声明会抛出哪些异常。在 Java 中,你必须明确指出每个方法会抛出哪些异常,如果你的某个工具方法需要抛出其他异常,你就得把这个信息加到所有调用它的方法里,这样做很快就会让人觉得无聊!

所以,如果你想知道某段 Python 代码会抛出哪些异常,就需要查看相关的文档和源代码。

不过,Python 的异常分类体系做得非常好。

如果你研究下面的异常分类,你会发现你想捕获的错误的父类叫做 StandardError——这可以捕获正常操作中可能产生的所有错误。把错误转换成字符串可以给用户一个合理的提示,告诉他们出了什么问题。因此,我建议你的代码应该像这样:

from shutil import move
try:
    move('somefile.txt', '/tmp/somefile.txt')
except StandardError, e:
    print 'Move failed: %s' % e

异常分类体系

BaseException
|---Exception
|---|---StandardError
|---|---|---ArithmeticError
|---|---|---|---FloatingPointError
|---|---|---|---OverflowError
|---|---|---|---ZeroDivisionError
|---|---|---AssertionError
|---|---|---AttributeError
|---|---|---BufferError
|---|---|---EOFError
|---|---|---EnvironmentError
|---|---|---|---IOError
|---|---|---|---OSError
|---|---|---ImportError
|---|---|---LookupError
|---|---|---|---IndexError
|---|---|---|---KeyError
|---|---|---MemoryError
|---|---|---NameError
|---|---|---|---UnboundLocalError
|---|---|---ReferenceError
|---|---|---RuntimeError
|---|---|---|---NotImplementedError
|---|---|---SyntaxError
|---|---|---|---IndentationError
|---|---|---|---|---TabError
|---|---|---SystemError
|---|---|---TypeError
|---|---|---ValueError
|---|---|---|---UnicodeError
|---|---|---|---|---UnicodeDecodeError
|---|---|---|---|---UnicodeEncodeError
|---|---|---|---|---UnicodeTranslateError
|---|---StopIteration
|---|---Warning
|---|---|---BytesWarning
|---|---|---DeprecationWarning
|---|---|---FutureWarning
|---|---|---ImportWarning
|---|---|---PendingDeprecationWarning
|---|---|---RuntimeWarning
|---|---|---SyntaxWarning
|---|---|---UnicodeWarning
|---|---|---UserWarning
|---GeneratorExit
|---KeyboardInterrupt
|---SystemExit

这也意味着,当你定义自己的异常时,应该基于 StandardError 而不是 Exception。

Base class for all standard Python exceptions that do not represent
interpreter exiting.
12

为了增强Messa的功能,要捕捉那些你预期会出现的错误情况,并且你知道如何处理这些错误。Ian Bicking写了一篇文章,讨论了一些总体原则,Eli Bendersky的笔记也有类似的内容。

这个示例代码的问题在于,它没有处理错误,只是把错误美化了一下然后丢掉了。你的代码并不知道该如何处理NameError,除了把它传递上去,基本上没有其他的做法。如果你觉得有必要添加细节,可以看看Bicking的重新抛出异常的内容。

IOError和OSError在使用shutil.move时是比较“常见”的错误,但不一定能处理。而调用你函数的人希望它能移动一个文件,如果这个“约定”被打破,他们可能会遇到问题,正如Eli所说的。

捕捉那些你能修复的错误,对于你预期但无法修复的错误,可以美化后重新抛出,让调用者来处理那些你没有预料到的错误,即使处理这些错误的代码在main中可能有七层之深。

撰写回答