raise 语句的 from 子句正是这样做的。它将原始异常包装在包装异常的__cause__ 属性中。这称为exception chaining,它是decorator design pattern 的一个应用程序。然后记录函数的exc_info参数允许记录原始异常:
import logging
try:
try:
raise IndexError("foo")
except IndexError as e:
raise KeyError from e
except KeyError as e:
logging.error("An exception occurred:", exc_info=e.__cause__)
用例
这种异常链接机制在某些情况下会派上用场。例如,当用户应该使用具有默认实现的接口的其他辅助方法来实现接口的抽象方法时。如果这些辅助方法可以引发异常,则其中一些异常可能会重叠(属于同一类型)。因此,实现的抽象方法的调用者将无法区分哪个辅助方法引发了异常。将原始异常包装在不同异常类的实例中可以解决此问题。
在这个示例代码中,我们有一个用于处理请求的BaseServer 接口和一个使用_parse、_format 和_send 辅助方法实现_handle 抽象方法的用户Server 类。我们使用异常链机制在_callback 方法(委托给_handle 方法)中根据_handle 方法实现中的故障点记录不同的消息:请求解析、请求处理、响应格式化或响应发送。
开发者代码:
import abc
import logging
class BaseServer(abc.ABCMeta):
def serve(self):
pass # serving code (should register the _callback method)
def _callback(self, request):
try:
logging.info("Received request: %s", request)
self._handle(request)
except ParsingError as e:
logging.error("Parsing failed:", exc_info=e.__cause__)
except FormattingError as e:
logging.error("Formatting failed:", exc_info=e.__cause__)
except SendingError as e:
logging.error("Sending failed:", exc_info=e.__cause__)
except Exception:
logging.exception("Processing failed:")
@abc.abstractmethod
def _handle(self, request):
raise NotImplementedError
@classmethod
def _parse(cls, request):
try:
pass # parsing code
except Exception as e:
raise ParsingError from e
@classmethod
def _format(cls, response):
try:
pass # formatting code
except Exception as e:
raise FormattingError from e
@classmethod
def _send(cls, response):
try:
pass # sending code
except Exception as e:
raise SendingError from e
class ParsingError(Exception):
pass
class FormattingError(Exception):
pass
class SendingError(Exception):
pass
用户代码:
class Server(BaseServer):
def _handle(self, request):
request = self._parse(request)
response = request # processing code
response = self._format(response)
self._send(response)