在函数中抛出异常与返回 None?

102 投票
5 回答
59882 浏览
提问于 2025-04-15 13:47

在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 个回答

6

我通常喜欢在内部处理异常,也就是说在被调用的函数里用try/except来处理,可能会返回一个None。这是因为Python是动态类型的。一般来说,我认为这是一种判断的选择,但在动态类型的语言中,有一些小因素让我更倾向于不把异常传递给调用者:

  1. 调用你函数的人并不知道可能会抛出哪些异常。要知道你在找什么样的异常,有点像是一种艺术(而且应该避免使用通用的except块)。
  2. if val is Noneexcept ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace要简单一些。说真的,我讨厌每次都要记得在我的Django文件顶部写from django.core.exceptions import ObjectDoesNotExist,只是为了处理一个非常常见的用例。在一个静态类型的世界里,让编辑器帮你处理这些事情。

老实说,这总是一个判断的选择,而你描述的情况,调用的函数遇到一个它无法处理的错误,是重新抛出一个有意义的异常的一个很好的理由。你的想法是完全正确的,但除非你的异常能在堆栈跟踪中提供比

AttributeError: 'NoneType' object has no attribute 'foo'

更有意义的信息,否则九成九的情况下,调用者看到的就是返回的未处理的None,那就没必要费这个劲了。

(这让我有点希望Python的异常默认就有cause属性,就像Java那样,这样你可以把异常传递到新的异常中,这样你就可以随意重新抛出,而不会失去问题的原始来源。)

8

在回答你的问题之前,我想给你几个建议,这些建议可能会直接帮助你解决问题。

  • 给你的函数起个好听的名字。比如,latestpdf这个名字对别人来说意义不大,但如果你看一下这个函数latestpdf(),它的作用是获取最新的pdf文件。我建议你把它改成getLatestPdfFromFolder(folder),这样更清楚。

一旦我这样做了,返回的内容就变得很明确了。如果没有pdf文件,就抛出一个异常。不过,还有更多建议……

  • 保持函数的定义清晰。因为有些函数的作用不明显,比如somefuc,它和获取最新pdf的关系也不明显,我建议你把它移出去。这样代码会更容易阅读。

for folder in folders:
   try:
       latest = getLatestPdfFromFolder(folder)
       results = somefuc(latest)
   except IOError: pass

希望这些建议对你有帮助!

106

这其实是个语义的问题。foo = latestpdf(d) 这句话 是什么意思 呢?

如果没有最新的文件是完全合理的,那就返回 None 吧。

如果你希望总是能找到最新的文件,那就抛出一个异常。没错,重新抛出一个更合适的异常也是可以的。

如果这个函数是一个通用的函数,应该适用于任何目录,我会选择前者,返回 None。但如果这个目录是特定的数据目录,里面应该有应用程序已知的一组文件,那我就会抛出异常。

撰写回答