python异常捕捉

“捕获” 指的是使用 try ... except 包裹特定语句,妥当的完成错误流程处理

raise 主动“抛出”异常,更是优雅代码里必不可少的组成部分

异常的基本语法与用法

Python 鼓励使用异常

参看 » https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/6-three-rituals-of-exceptions-handling.md

要精确每个步骤的异常 step by step

永远只捕获那些可能会抛出异常的语句块

尽量只捕获精确的异常类型,而不是模糊的 Exception


# demo 
from requests.exceptions import RequestException

def save_website_title(url, filename):
    try:
        resp = requests.get(url)
    except RequestException as e:
        print(f'save failed: unable to get page content: {e}')
        return False


    obj = re.search(r'<title>(.*)</title>', resp.text)
    if not obj:
        print('save failed: title tag not found in page content')
        return False
    title = obj.group(1)

    try:
        with open(filename, 'w') as fp:
            fp.write(title)
    except IOError as e:
        print(f'save failed: unable to write to file {filename}: {e}')
        return False
    else:
        return True


避免抽象级的异常被具体模块抛出

比如一个web服务端定义了一个抽象级总的 APIError异常父类

还需要定义 具体的子异常 Error_xxx, Error_ooo, Error_xxo..

模块只抛出与当前抽象层级一致的异常

# <PROJECT_ROOT>/util/image/processor.py
class ImageOpenError(Exception):
    pass


def process_image(...):
    try:
        image = Image.open(fp)
    except Exception as e:
        raise ImageOpenError(exc=e)
    ... ...
    
# <PROJECT_ROOT>/app/views.py
def foo_view_function(request):
    try:
        process_image(fp)
    except ImageOpenError:
        raise error_codes.INVALID_IMAGE_UPLOADED

在必要的地方进行异常包装与转换

应该避免泄露低于当前抽象级别的异常

requests 模块 请求页面出错时所抛出的异常,不是它在底层所使用的 urllib3 模块的原始异常,
而是通过 requests.exceptions 包装过一次的异常
try:
    requests.get('https://www.invalid-host-foo.com')
except Exception as e:
    print(type(e))

# <class 'requests.exceptions.ConnectionError'>

避免扰乱核心代码 (使用上下文管理器)

避免代码里充斥着大量的 try、except、raise 语句,让核心逻辑变得难以辨识

上下文管理器(context manager)是一种配合 with 语句使用的特殊 Python 对象, 通过它,可以让异常处理工作变得更方便


# 根据error 统一具体的处理异常


from enum import IntEnum

class Common(Exception):
    ...

class TestError(Common):
    def __init__(self, code):
        self.code = code
    ...

class ErrorEnum(IntEnum):
    SERVER_ERROR = 100000
    TEST_ERROR   = 100001

    ...


class ErrorManager:
    #
    def __init__(self, exception, error_code=ErrorEnum.SERVER_ERROR):
        self.exception = exception
        self.error_code = error_code

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return False

        if exc_type == self.exception:
            ...
            error_message = ErrorEnum(self.error_code).name
            print(error_message)
            #   做异常处理工作, log, 返回值
            return True

        if issubclass(exc_type, Exception):
            ...
            print(exc_value)
            error_message = f"SERVER_ERROR: {exc_value}"
            print(error_message, traceback)

            #   做异常处理工作, log, 返回值
            return True





# 定义了一个名为 ErrorManager 的上下文管理器,
# 它在进入上下文时什么也不做。
# 但是在退出上下文时,会判断当前上下文中是否抛出了类型为 self.exception 的异常,


with ErrorManager(TestError, ErrorEnum.TEST_ERROR):
    ...
    # 实际代码逻辑
    # raise TestError(100001)
    int("12312doasd")


Buy me a 肥仔水!