复杂函数的Pythonic错误处理

时间:2011-04-04 06:53:54

标签: error-handling python

我想知道是否有一种Pythonic方法可以处理长时间运行的函数中的错误,这些函数可能会出现部分错误而不会影响函数继续运行的能力。

作为示例,考虑一个给出URL列表的函数,它递归地检索顶级URL路径下的资源和所有链接资源。它将检索到的资源存储在本地文件系统中,其目录结构镜像URL结构。基本上,这是页面列表的基本递归wget。

此功能可能会失败的点很多:

  • 网址可能无效或无法解析
  • 主机可能无法访问(可能是暂时的)
  • 本地保存可能有磁盘错误
  • 你能想到的任何其他事情。

检索或保存任何一个资源失败只会影响该函数继续处理该资源的能力以及可能与之链接的任何子资源,但可以继续检索其他资源。

错误处理的一个简单模型是,在第一个错误上,会引发一个适当的异常以供调用者处理。这个问题是它终止了函数并且不允许它继续。错误可能已修复,函数从头开始重新启动,但这会导致重做工作,任何永久性错误都可能意味着我们永远无法完成。

我想到的几个选择是:

  • 在列表发生错误时记录错误,并中止处理该资源的任何子资源,但继续执行下一个资源。如果发生太多错误,可以使用阈值来中止整个功能,或者只是尝试一切。调用者可以在函数完成时查询此列表,以查看是否存在任何问题。
  • 调用者可以提供每个错误调用的可调用对象。这将记录错误的责任移回给调用者。你甚至可以指定如果callable返回False,那么处理应该停止。这会将阈值管理移至调用者。
  • 使用后者实现前者,提供错误处理对象,而不是编码前者的行为。

在Python讨论中,我经常注意到某些方法被描述为Pythonic或非Pythonic。我想知道是否有任何特别的Pythonic方法来处理上述场景类型。

Python是否包含任何模型,比异常处理的终止模型更复杂的错误处理模型,或者更复杂的电池是否使用我应该复制以保留Pythonic的错误处理模型?

注意:请不要专注于示例。我不打算在那个特定的空间里解决问题,但这似乎是一个很好的例子,大多数人都会对此有所了解。

2 个答案:

答案 0 :(得分:6)

我不认为你在这里谈论的水平上有一个特别清晰的“Pythonic / non-Pythonic”区别。

在这个领域没有“一刀切”解决方案的一个重要原因是,你想要的确切语义将是特定于问题的。

  • 对于一种情况,首先失败可能就足够了。
  • 另一方面,如果任何操作失败,您可能需要中止并回滚。
  • 对于第三个,您可能希望尽可能多地完成并简单地记录并忽略失败
  • 对于第四种选择,您可能希望尽可能多地完成,但最后提出异常以报告任何失败。

即使支持错误处理程序也不一定涵盖所有这些所需的行为 - 简单的每次失败错误处理程序不能轻易提供中止和回滚语义,或者最终生成单个异常。 (这不是不可能的 - 你只需要处理诸如传递绑定方法或闭包作为错误处理程序的技巧)

因此,您可以做的最好的事情就是在面对错误时对典型的使用场景和理想行为进行有根据的猜测,并相应地设计您的API。

一个完全通用的解决方案将接受一个错误处理程序,该错误处理程序在发生时发生每个失败,并且最终发生“错误”处理程序,使调用者有机会决定如何处理多个错误(允许一些协议)要从各个错误处理程序传递到最终批处理错误处理程序的数据。)

但是,提供这样的通用解决方案很可能是API设计失败。 API的设计者不应该害怕对如何使用API​​以及如何处理错误发表意见。要记住的主要事情是不要过度设计你的解决方案:

  • 如果天真的做法足够,不要搞乱它
  • 如果在列表中收集失败并报告单个错误就足够了,那就
  • 如果你需要回滚所有部分,如果一个部分失败,那么就这样实施
  • 如果有自定义错误处理的真实用例,则接受错误处理程序作为API的一部分。但是当你这样做时,请记住一个特定的用例,不要只是为了它而做。当你这样做时,有一个明智的默认处理程序,如果用户没有指定一个(这可能只是天真的“立即提升”方法)
  • 如果您提供可选择的错误处理程序,请考虑提供一些标准错误处理程序,这些处理程序可以作为callables或命名字符串传递(即沿着文本编解码器的错误处理程序选择行)

作为一般原则,你可能得到的最好的结果是“Pythonic”错误处理将尽可能简单,但并不简单。但就此而言,这个词只是被用作“好代码”的同义词,而这并不是它的意图。

另一方面,谈论非Pythonic错误处理的实际形式可能会稍微容易一些:

def myFunction(an_arg, error_handler)
  # Do stuff
  if err_occurred:
    if isinstance(err, RuntimeError):
      error_handler.handleRuntimeError()
    elif  isinstance(err, IOError):
      error_handler.handleIOError()

Pythonic成语是错误处理程序,如果支持的话,只是简单的callables。向他们提供他们决定如何处理这种情况所需的信息,而不是试图为他们做出太多决定。如果您希望更容易实现错误处理的常见方面,那么提供一个单独的帮助器类,使用__call__方法执行调度,以便人们可以决定是否要使用它(或者如何当他们使用它时他们想要覆盖多少)。这不完全是特定于Python的,但来自语言的东西,这使得传递任意可调用的烦恼(例如Java,C,C ++)变得非常困难。因此,复杂的错误处理协议绝对是进入“非Pythonic错误处理”领域的一种方式。

上述非Pythonic代码中的另一个问题是没有提供默认处理程序。迫使每个API用户做出他们可能尚未配备的决定只是糟糕的API设计。但现在我们回到了一般的“好代码”/“坏代码”领域,所以不应该用Pythonic /非Pythonic来描述差异。

答案 1 :(得分:0)

错误处理应依赖于异常和日志记录,因此对于每个错误都会引发异常并记录错误消息。

然后在任何调用者函数级别捕获异常,如果需要,记录任何其他附加错误并处理该问题。

如果问题没有得到完全处理,那么re-raise the exception again以便上层可以捕获相同的异常并执行不同的操作。

在任何这些阶段中,您都可以对某些类型的异常进行计数,以便只有在出现特定数量的问题时才能执行某些操作。

相关问题