KeyboardInterrupt异常。而KeyErrorValueErrorTypeError一些常见的异常。

异常处理工作由“捕获”和“抛出”两部分组成。“捕获”指的是使用 try ... except 包裹特定语句,妥当的完成错误流程处理。而恰当的使用 raise 主动“抛出”异常,更是优雅代码里必不可少的组成部分。

阅读前最好了解

  • 异常的基本语法与用法*(建议阅读官方文档 )*
  • 为什么要使用异常代替错误返回*(建议阅读Python 函数 工匠 一)*
  • 为什么在写 Python 时鼓励使用异常

好习惯

尽量做最精确的异常捕获

假如你不够了解异常机制,就难免会对它有一种天然恐惧感。你可能会觉得:异常是一种不好的东西,好的程序就应该捕获所有的异常,让一切都平平稳稳的运行。 而抱着这种想法写出的代码,里面通常会出现大段含糊的异常捕获逻辑。但是,某些时候你需要捕捉所有异常Exception

import requests
import re


def save_website_title(url, filename):
    """获取某个地址的网页标题,然后将其写入到文件中
    
    :returns: 如果成功保存,返回 True,否则打印错误,返回 False
    """
    try:
        resp = requests.get(url)
        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.grop(1)
        with open(filename, 'w') as fp:
            fp.write(title)
            return True
    except Exception:
        print(f'save failed: unable to save title of {url} to {filename}')
        return False


def main():
    save_website_title('https://www.qq.com', 'qq_title.txt')


if __name__ == '__main__':
    main()

脚本里的 save_website_title 函数做了好几件事情。它首先通过网络获取网页内容,然后利用正则匹配出标题,最后将标题写在本地文件里。而这里有两个步骤很容易出错:网络请求本地文件操作。所以在代码里,我们用一个大大的 try ... except 语句块,将这几个步骤都包裹了起来。安全第一 ⛑。

那么,这段看上去简洁易懂的代码,里面藏着什么问题呢?

如果尝试运行上面一段代码。你会发现,上面的代码是不能成功执行的。而且你还会发现,无论你如何修改网址和目标文件的值,程序仍然会报错 “save failed: unable to...”。为什么呢?

问题就藏在这个硕大无比的 try ... except 语句块里。假如你把眼睛贴近屏幕,非常仔细的检查这段代码。你会发现在编写函数时,我犯了一个小错误,我把获取正则匹配串的方法错打成了 obj.grop(1),少了一个 'u'(obj.group(1))。

但正是因为那个过于庞大、含糊的异常捕获,这个由打错方法名导致的原本该被抛出的 AttibuteError 却被吞噬了。从而给我们的 debug 过程增加了不必要的麻烦。

异常捕获的目的,不是去捕获尽可能多的异常。假如我们从一开始就坚持:只做最精准的异常捕获。那么这样的问题就根本不会发生,精准捕获包括:

  • 永远只捕获那些可能会抛出异常的语句块
  • 尽量只捕获精确的异常类型,而不是模糊的 Exception

依照这个原则,我们的样例应该被改成这样:

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

    # 这段正则操作本身就是不应该抛出异常的,所以我们没必要使用 try 语句块
    # 假如 group 被误打成了 grop 也没关系,程序马上就会通过 AttributeError 来
    # 告诉我们。
    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