Python 进阶之错误捕获与异常处理

一、异常介绍

Python 中的异常是一种由 raise 语句自动创建的对象。在异常对象生成之后,Python 程序不会再继续执行 raise 后面紧跟的语句或者操作异常对象本身,而是“搜索”用于处理该异常的某个特定的 handler
如果该 handler 被 Python 程序找到,则它可以关联并访问异常对象获取更多的信息;
如果没有找到与异常对应的 handler,则程序终止并输出错误信息。

PS: LBYL 与 EAFP
从理念上讲,Python 倾向于在错误发生之后通过捕获异常来处理程序错误。称为 easier to ask forgiveness than permission (EAFP)
另外一种错误处理的方式则是尽可能地在错误发生之前检查所有可能发生的情况,这种模式称为 look before you leap (LBYL)

Python 提供多种不同类型的异常用以反映错误产生的原因和场景等。每种类型的异常实际上都是一个 Python 类,且其中大多数都继承于 Exception 。对于用户自定义的异常类,也最好作为 Exception 的子类来实现。

Python 异常触发时通常会输出以下内容:

>>> alist = [1,2,3]
>>> alist[7]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

上面代码中的 alist[7] 在请求列表中的项目时超出了列表原本的长度,因此触发了 IndexError 异常。该异常被 Python 交互解释器获取并处理,最终输出错误信息。

如果需要,其实也可以在代码中通过 raise 语句手动触发异常:

>>> raise IndexError("Just kidding")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: Just kidding

二、异常捕获

能够终止程序的运行并输出错误信息并不是异常机制的关键所在。异常机制的特殊性在于,通过定义合适的用于处理特定异常的 handler,可以确保一般的异常情况能够被 handler 捕捉,而不会直接导致程序运行失败。
handler 可以输出错误信息给用户,或者尝试修复问题,但是重点在于它不会终止程序。

捕获异常的基本语法如下:

try:
    body
except exception_type1 as var1:
    exception_code1
except exception_type2 as var2:
    exception_code2
...
except:
    default_exception_code
else:
    else_body
finally:
    finally

其具体的执行流程如下:

  1. try 语句首先被执行,如果执行成功(没有抛出异常),则继续执行 else 语句(如果有),try 语句此时结束。最后执行 finally 语句(如果有)。
  2. 如果 try 语句执行时抛出异常,则 except 语句根据异常类型进行匹配。如某个 except 语句最终匹配抛出的异常类型,则抛出的异常赋值给其后的变量 var(如果有),对应的 exception_code 被执行。
  3. 如果 try 抛出的异常没有任何 except 语句进行匹配,则该异常继续传递看是否有内嵌的 try 语句对其进行处理。
  4. 上面格式中最后的 except 语句没有跟任何异常类型,则表示它将匹配关联所有的异常类型。这种方式在调试和创建程序原型时较常用,但并不推荐(因为隐藏了异常包含的细节)。
  5. try 语句中的 else 是可选的且并不常用,它只有在 try 语句执行后未抛出任何异常的情况下才会执行。
  6. finally 语句同样是可选的,它在任何情况下最终都会执行。即便 try 语句抛出异常且没有任何 except 语句进行捕获和处理,该异常也是在 finally 语句执行后抛出给用户。
    因此 finally 语句多用于一些“清理”任务,比如读写硬盘后的关闭文件等。如:
try:
    infile = open(filename)
    data = infile.read()
finally:
    infile.close()

三、assert 语句

assert 语句是 raise 语句的一种特殊形式,其语法格式如下:
assert expression, argument

其含义为,如果 expression 表达式的执行结果为 False 且系统变量 __debug__ 的值为 True,则抛出 AssertionError 异常和 argument变量(可选)。

系统变量 __debug__ 的值默认为 True,可以在 Python 运行时添加 -O-OO 选项将该值改为 False
因此可以在开发程序时加入 assert 语句进行调试,而程序发布时将其保留在代码中也不会产生任何影响(只需保证 __debug__False)。

>>> x = (1, 2, 3)
>>> assert len(x) > 5, "len(x) not > 5"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: len(x) not > 5

四、代码示例

不捕获异常
while True:
    x = int(input('Enter the first number: '))
    y = int(input('Enter the second number: '))
    print(x / y)

运行效果(出错后程序退出):

$ python exceptions.py
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number: 4
Enter the second number: 0
Traceback (most recent call last):
  File "exception.py", line 4, in <module>
    print(x / y)
ZeroDivisionError: division by zero
$
捕获除数不为零异常
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except ZeroDivisionError:
        print('Division by zero is illegal')

运行效果(出错后异常被捕获,程序不退出):

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:
捕获多个异常
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except ZeroDivisionError:
        print('Division by zero is illegal')
    except ValueError:
        print("That wasn't a number, was it?")

运行效果:

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 4
Enter the second number: hello
That wasn't a number, was it?
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:
捕获所有异常(不推荐,隐藏了异常的细节)
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except:
        print('Something wrong happened ...')

运行效果:

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Something wrong happened ...
Enter the first number: 4
Enter the second number: hello
Something wrong happened ...
Enter the first number:

稍好一点的版本(输出异常信息):

while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except Exception as e:
        print('Invalid input:', e)

运行效果:

Enter the first number: 4
Enter the second number: 0
Invalid input: division by zero
Enter the first number: 4
Enter the second number: hello
Invalid input: invalid literal for int() with base 10: 'hello'
Enter the first number:

参考资料

Beginning Python 3rd
The Quick Python Book 3rd Edition

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容