【问题标题】:How to handle unhandled exceptions?如何处理未处理的异常?
【发布时间】:2013-05-07 14:25:45
【问题描述】:

我正在清理一个 API 库,并试图找出处理未处理异常的最佳方法。

目前,该库几乎可以捕获 API 可能出现的所有问题——凭据错误、服务器错误、urllib2 错误、httplib 错误等。将会有边缘情况。

我目前的想法是 99% 的库用户并不关心异常本身,他们只关心 API 调用失败。只有开发人员会关心异常。

这让我想到了这个解决方案:

class ApiError(Exception):
    pass

class ApiUnhandledError(ApiError):
    pass

API 的已知问题会引发 ApiError 或特定子类。

其他一切都会引发 ApiUnhandledError ,其中隐藏了原始错误,用户可以捕获或忽略它。

try:
    stuff
except urllib2.UrlError , e :
    raise ApiError(raised=e)
except Exception as e :
    raise ApiUnhandledError(raised=e)

这听起来像是确保用户只知道 Pass/Fail 的好方法,而开发人员可以维护一种支持方法吗?

更新

基于最佳实践的共识,我不会陷入困境。

最初的目标是让人们这样做:

try:
   stuff_a
   other_stuff
   even_more_stuff

   api.proxy(url)

   again_stuff
   again_others
 except api.ApiUnhandledError , e :
    handle error
 except api.ApiError , e :
    handle error
 except Stuff , e :
     other error
 except:
     raise

在该示例中,用户只需捕获 ApiError(以及可选的 ApiUnhandledError 或任何其他子类)

我认为这在很大程度上优于每个 api 交互都有自己的块:

try:
   stuff_a
   other_stuff
   even_more_stuff

   try:
       api.proxy(url)
     except api.ApiError , e :
        handle error
     except CatchSomething1 , e :
        handle error
     except CatchSomething2 , e :
        handle error
     except CatchSomething3 , e :
        handle error
     except CatchSomething4 , e :
        handle error
     except:
        raise

   again_stuff
   again_others
 except Stuff , e :
     other error
 except:
     raise

在处理 urllib2 时,我似乎每天都发现一个新异常。这些异常往往会变得很长且难以维护。

【问题讨论】:

  • 您确定要使用这样的机制吗?你只会用你的“假”例外来掩盖一个真正的例外。我只想留下未处理的异常,因为它们是最能提供信息的。如果您想将它们记录在某处或创建一般操作,那就是另一回事了。
  • 我不确定我想这样做。我担心的是有一些错误代码可能会从一个底层库中弹出。查看我自己使用这个库的“消费者”代码,我不得不将对该库的调用嵌套在它自己的块中,然后围绕它链接大量的异常处理来捕获这些错误。我正在尝试将这种异常处理移植到上游,这似乎是一种潜在的方式,可以最大限度地减少消费者的大部分工作 - 同时仍然保留异常。

标签: python exception-handling


【解决方案1】:

如果您的库引发了您没有预见到并且您没有处理的异常,那就这样吧。这可能是库错误的标志,否则会被忽略。如果您可以明确错误的原因,则可以捕获并重新启动(例如,在身份验证器方法中捕获 socket.error 并重新启动 AuthenticationFailedError),但向库用户掩盖故障点是不公平的(他们自己就是程序员)。

用户不应尝试处理或消除直接来自库内部的错误(即不是您的 API 的一部分——大致上,不是由您编写或提出的),因为它们是内部那段代码。如果库作者忘记处理它们或捕获并重新启动一个更具体的,这是一个错误,应该报告。如果函数对输入做出假设(例如,为了速度),则应在 API 参考中明确说明,任何违反都是用户的错。

在 Python2 中,只能产生一个经典类或继承自 BaseException 的新样式类,在 Python3 中经典类已不存在,因此您的库可能产生的任何东西都必须从 BaseException 继承。用户通常想要处理的任何异常(这不包括 SystemExit、GeneratorExit、KeyboardInterrupt 等,它们构成特殊情况)必须从 Exception 继承。尝试提升时会报告失败:

Python3:

>>> class Test:
...     pass
... 
>>> raise Test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: exceptions must derive from BaseException

Python2:

>>> class Test(object):
...     pass
... 
>>> raise Test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: exceptions must be old-style classes or derived from BaseException, not Test
>>> 

也就是说,您不需要将异常包装在 UnhandledException 容器中,只需让代码中的所有异常都继承自 Exception。屏蔽失败是不好的做法(你不应该鼓励这样做),但懒惰的用户仍然可以利用从 Exception 基类继承的优势,并通过以下方式捕获所有未处理的异常:

try:
    yourapi.call_me()
except APIKnownError as error:
    report(error)
except Exception as error:
    pass
    # Or whatever

值得注意的是,Python 提供了一个warnings 模块,用于报告“程序中的某些条件,该条件(通常)不需要引发异常并终止程序”。

说到应用程序框架(不是库),我非常喜欢Tornado 方法:它将记录所有未处理的异常并在错误不严重时继续。我认为这个决定应该由最终用户决定。

【讨论】:

  • 在你的例子中,我正在思考这个问题:我捕获了一个 socket.error 异常,然后以 socket.error 的实例作为属性引发 ApiUnhandledError。我不想隐藏故障点,我只想让图书馆用户有一个“更轻松”的时间来处理错误。如果他们关心根本问题是什么,他们可以轻松检查 - API 引发了异常,该异常存储原始引发。由于这个库使用了令人讨厌的 urllib2 包,我将所有可能发生的随机边缘情况(除了 9 个显式捕获)合并到一个 ApiUnhandledError 中。
  • @JonathanVanasco,我不会将异常包装在另一个异常中,因为这会向用户暴露内部依赖关系:如果您想使用自己的错误从 socket 切换到更高级的库怎么办类型?用户将不得不无缘无故地重写他/她的处理代码。我会提取相关信息并使用它们来构建您的异常。还要检查编辑。
  • Python 2 中的用户异常应该派生自 Exception,而不是 BaseException docs.python.org/2/library/exceptions.html#exceptions.Exception
  • 确实如此(我在答案中提到过)。但是没有什么禁止你继承BaseException。为清楚起见,将“will”替换为“must”。
猜你喜欢
  • 2012-07-26
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
相关资源
最近更新 更多