如何查找Python中某个库函数可能抛出的所有异常列表?
抱歉标题有点长,但我觉得这样描述我的问题最清楚。
简单来说,我在官方的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 个回答
是的,你可以这样做(对于简单的情况),但需要一点元编程的知识。就像其他回答提到的,函数并不会声明它会抛出特定类型的错误,所以你需要查看这个模块,看看它定义了哪些异常类型,或者它会抛出哪些异常类型。你可以尝试理解文档,或者利用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
定义了错误类型 Error
和 WindowsError
,并且抛出了异常类型 OSError
和 Error
。如果我们想更全面一点,可以再写一个方法,检查每个 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
目前,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.
为了增强Messa的功能,要捕捉那些你预期会出现的错误情况,并且你知道如何处理这些错误。Ian Bicking写了一篇文章,讨论了一些总体原则,Eli Bendersky的笔记也有类似的内容。
这个示例代码的问题在于,它没有处理错误,只是把错误美化了一下然后丢掉了。你的代码并不知道该如何处理NameError,除了把它传递上去,基本上没有其他的做法。如果你觉得有必要添加细节,可以看看Bicking的重新抛出异常的内容。
IOError和OSError在使用shutil.move
时是比较“常见”的错误,但不一定能处理。而调用你函数的人希望它能移动一个文件,如果这个“约定”被打破,他们可能会遇到问题,正如Eli所说的。
捕捉那些你能修复的错误,对于你预期但无法修复的错误,可以美化后重新抛出,让调用者来处理那些你没有预料到的错误,即使处理这些错误的代码在main
中可能有七层之深。