在函数中抛出异常与返回 None?
在Python中,自己定义的函数里,使用raise
抛出一个异常好,还是return None
返回空值好呢?比如,我有一个函数是用来找文件夹里最新的文件。
def latestpdf(folder):
# list the files and sort them
try:
latest = files[-1]
except IndexError:
# Folder is empty.
return None # One possibility
raise FileNotFoundError() # Alternative
else:
return somefunc(latest) # In my case, somefunc parses the filename
还有一种选择是留下异常,然后在调用这个函数的地方处理它。不过我觉得处理FileNotFoundError
(文件未找到错误)比处理IndexError
(索引错误)要清楚一些。或者说,用不同的名字重新抛出异常是不太好的做法吗?
5 个回答
我通常喜欢在内部处理异常,也就是说在被调用的函数里用try/except来处理,可能会返回一个None。这是因为Python是动态类型的。一般来说,我认为这是一种判断的选择,但在动态类型的语言中,有一些小因素让我更倾向于不把异常传递给调用者:
- 调用你函数的人并不知道可能会抛出哪些异常。要知道你在找什么样的异常,有点像是一种艺术(而且应该避免使用通用的except块)。
if val is None
比except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace
要简单一些。说真的,我讨厌每次都要记得在我的Django文件顶部写from django.core.exceptions import ObjectDoesNotExist
,只是为了处理一个非常常见的用例。在一个静态类型的世界里,让编辑器帮你处理这些事情。
老实说,这总是一个判断的选择,而你描述的情况,调用的函数遇到一个它无法处理的错误,是重新抛出一个有意义的异常的一个很好的理由。你的想法是完全正确的,但除非你的异常能在堆栈跟踪中提供比
AttributeError: 'NoneType' object has no attribute 'foo'
更有意义的信息,否则九成九的情况下,调用者看到的就是返回的未处理的None,那就没必要费这个劲了。
(这让我有点希望Python的异常默认就有cause
属性,就像Java那样,这样你可以把异常传递到新的异常中,这样你就可以随意重新抛出,而不会失去问题的原始来源。)
在回答你的问题之前,我想给你几个建议,这些建议可能会直接帮助你解决问题。
- 给你的函数起个好听的名字。比如,
latestpdf
这个名字对别人来说意义不大,但如果你看一下这个函数latestpdf()
,它的作用是获取最新的pdf文件。我建议你把它改成getLatestPdfFromFolder(folder)
,这样更清楚。
一旦我这样做了,返回的内容就变得很明确了。如果没有pdf文件,就抛出一个异常。不过,还有更多建议……
- 保持函数的定义清晰。因为有些函数的作用不明显,比如somefuc,它和获取最新pdf的关系也不明显,我建议你把它移出去。这样代码会更容易阅读。
for folder in folders:
try:
latest = getLatestPdfFromFolder(folder)
results = somefuc(latest)
except IOError: pass
希望这些建议对你有帮助!
这其实是个语义的问题。foo = latestpdf(d)
这句话 是什么意思 呢?
如果没有最新的文件是完全合理的,那就返回 None
吧。
如果你希望总是能找到最新的文件,那就抛出一个异常。没错,重新抛出一个更合适的异常也是可以的。
如果这个函数是一个通用的函数,应该适用于任何目录,我会选择前者,返回 None
。但如果这个目录是特定的数据目录,里面应该有应用程序已知的一组文件,那我就会抛出异常。